From c2b9cd8d1f1a81af32c757063a460815ff4949a4 Mon Sep 17 00:00:00 2001 From: StellaOps Bot Date: Fri, 26 Dec 2025 21:54:17 +0200 Subject: [PATCH] Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements. --- .actrc | 47 + .gitea/README.md | 279 + .gitea/config/path-filters.yml | 533 + .gitea/docs/architecture.md | 432 + .gitea/docs/scripts.md | 736 + .gitea/docs/troubleshooting.md | 624 + .../scripts/release/bump-service-version.py | 350 + .gitea/scripts/release/collect_versions.py | 259 + .gitea/scripts/release/generate-docker-tag.sh | 130 + .gitea/scripts/release/generate_changelog.py | 448 + .gitea/scripts/release/generate_compose.py | 373 + .gitea/scripts/release/generate_suite_docs.py | 477 + .../scripts/release/read-service-version.sh | 131 + .gitea/scripts/release/rollback.sh | 226 + .gitea/scripts/test/run-test-category.sh | 299 + .../scripts/validate/validate-migrations.sh | 260 + .gitea/workflows/container-scan.yml | 227 + .gitea/workflows/dependency-license-gate.yml | 204 + .gitea/workflows/dependency-security-scan.yml | 249 + .gitea/workflows/migration-test.yml | 512 + .gitea/workflows/nightly-regression.yml | 483 + .gitea/workflows/release-suite.yml | 233 +- .gitea/workflows/renovate.yml | 114 + .gitea/workflows/rollback.yml | 277 + .gitea/workflows/sast-scan.yml | 386 + .gitea/workflows/secrets-scan.yml | 105 + .gitea/workflows/service-release.yml | 490 + .gitea/workflows/templates/replay-verify.yml | 267 + .gitea/workflows/test-matrix.yml | 763 +- .gitignore | 12 +- CLAUDE.md | 21 +- Directory.Build.props | 105 - StellaOps.Router.slnx | 17 - StellaOps.Tests.slnx | 2 - build_output_latest.txt | 55 + devops/ci-local/.env.local.sample | 147 + devops/ci-local/events/pull-request.json | 48 + devops/ci-local/events/push-main.json | 54 + devops/ci-local/events/release-tag.json | 21 + devops/ci-local/events/schedule.json | 22 + devops/ci-local/events/workflow-dispatch.json | 31 + devops/compose/docker-compose.ci.yaml | 130 + devops/compose/docker-compose.dev.yaml | 1 + .../compose/postgres-init/01-extensions.sql | 76 +- .../compose/postgres-init/02-create-users.sql | 53 + .../postgres-init/03-grant-permissions.sql | 153 + .../repro-builders/BUILD_ENVIRONMENT.md | 318 + .../docker/repro-builders/alpine/Dockerfile | 62 + .../repro-builders/alpine/scripts/build.sh | 226 + .../alpine/scripts/extract-functions.sh | 71 + .../alpine/scripts/normalize.sh | 65 + .../docker/repro-builders/debian/Dockerfile | 59 + .../repro-builders/debian/scripts/build.sh | 233 + .../debian/scripts/extract-functions.sh | 67 + .../debian/scripts/normalize.sh | 29 + devops/docker/repro-builders/rhel/Dockerfile | 85 + .../rhel/mock/stellaops-repro.cfg | 71 + .../repro-builders/rhel/scripts/build.sh | 213 + .../rhel/scripts/extract-functions.sh | 73 + .../repro-builders/rhel/scripts/mock-build.sh | 34 + .../repro-builders/rhel/scripts/normalize.sh | 83 + devops/releases/service-versions.json | 143 + devops/scripts/efcore/Scaffold-AllModules.ps1 | 93 + devops/scripts/efcore/Scaffold-Module.ps1 | 162 + devops/scripts/efcore/scaffold-all-modules.sh | 88 + devops/scripts/efcore/scaffold-module.sh | 113 + devops/scripts/fix-duplicate-packages.ps1 | 100 + devops/scripts/fix-duplicate-projects.ps1 | 55 + .../scripts/fix-duplicate-using-testkit.ps1 | 55 + devops/scripts/fix-missing-xunit.ps1 | 51 + devops/scripts/fix-project-references.ps1 | 44 + devops/scripts/fix-sln-duplicates.ps1 | 68 + devops/scripts/fix-xunit-using.ps1 | 40 + devops/scripts/fix-xunit-v3-conflict.ps1 | 37 + devops/scripts/generate-plugin-configs.ps1 | 247 + devops/scripts/lib/ci-common.sh | 406 + devops/scripts/lib/ci-docker.sh | 342 + devops/scripts/lib/ci-web.sh | 475 + devops/scripts/lib/exit-codes.sh | 178 + devops/scripts/lib/git-utils.sh | 262 + devops/scripts/lib/hash-utils.sh | 266 + devops/scripts/lib/logging.sh | 181 + devops/scripts/lib/path-utils.sh | 274 + devops/scripts/local-ci.ps1 | 264 + devops/scripts/local-ci.sh | 818 + devops/scripts/migrations-reset-pre-1.0.sql | 244 + devops/scripts/regenerate-solution.ps1 | 169 + devops/scripts/remove-stale-refs.ps1 | 70 + devops/scripts/restore-deleted-tests.ps1 | 61 + devops/scripts/validate-before-commit.ps1 | 176 + devops/scripts/validate-before-commit.sh | 318 + docs/07_HIGH_LEVEL_ARCHITECTURE.md | 3 +- docs/10_PLUGIN_SDK_GUIDE.md | 6 + .../ACCESSIBILITY_AUDIT_VEX_TRUST_COLUMN.md | 215 + ...VEX_SIGNATURE_VERIFICATION_OFFLINE_MODE.md | 384 + docs/attestor/cosign-interop.md | 308 + docs/cicd/README.md | 329 + docs/cicd/path-filters.md | 414 + docs/cicd/release-pipelines.md | 509 + docs/cicd/security-scanning.md | 508 + docs/cicd/test-strategy.md | 461 + docs/cicd/workflow-triggers.md | 719 + docs/db/MIGRATION_CONVENTIONS.md | 305 + docs/db/MIGRATION_STRATEGY.md | 63 +- docs/guides/vex-trust-gate-rollout.md | 223 + ...1229_0001_0001_full_pipeline_validation.md | 595 + .../SPRINT_1229_000_SBOM_SOURCES_OVERVIEW.md | 362 + ...INT_1229_001_BE_sbom-sources-foundation.md | 584 + ...PRINT_1229_002_BE_sbom-sources-triggers.md | 638 + .../SPRINT_1229_003_FE_sbom-sources-ui.md | 523 + ...PRINT_20251226_015_AI_zastava_companion.md | 1 + ...SPRINT_20251226_016_AI_remedy_autopilot.md | 1 + .../SPRINT_20251226_017_AI_policy_copilot.md | 1 + .../SPRINT_20251226_018_AI_attestations.md | 1 + ...PRINT_20251226_019_AI_offline_inference.md | 1 + .../SPRINT_20251226_020_FE_ai_ux_patterns.md | 1 + .../SPRINT_20251226_001_CICD_gitea_scripts.md | 0 ..._20251226_002_CICD_devops_consolidation.md | 0 .../SPRINT_20251226_003_CICD_test_matrix.md | 0 ...INT_20251226_004_CICD_module_publishing.md | 0 .../SPRINT_20251226_005_CICD_suite_release.md | 0 .../SPRINT_20251226_006_CICD_local_docker.md | 0 ...INT_20251226_007_CICD_test_coverage_gap.md | 0 .../2025-12-27-dal-consolidation/README.md | 92 + ...1227_0001_0000_dal_consolidation_master.md | 206 + .../SPRINT_1227_0002_0001_dal_notify.md | 113 + .../SPRINT_1227_0002_0002_dal_scheduler.md | 70 + .../SPRINT_1227_0002_0003_dal_taskrunner.md | 69 + .../SPRINT_1227_0003_0001_dal_authority.md | 94 + .../SPRINT_1227_0004_0001_dal_scanner.md | 108 + .../SPRINT_1227_0005_0001_dal_concelier.md | 99 + .../SPRINT_1227_0006_0001_dal_policy.md | 77 + .../SPRINT_1227_0006_0002_dal_signals.md | 69 + .../SPRINT_1227_0007_0001_dal_excititor.md | 70 + .../SPRINT_1227_0007_0002_dal_vexhub.md | 69 + ...INT_1227_0007_0003_dal_issuer_directory.md | 71 + ...PRINT_1227_0008_0001_dal_packs_registry.md | 72 + .../SPRINT_1227_0008_0002_dal_sbom_service.md | 69 + .../SPRINT_1227_0008_0003_dal_airgap.md | 77 + .../SPRINT_1227_0009_0001_dal_graph.md | 69 + .../SPRINT_1227_0009_0002_dal_evidence.md | 80 + .../SPRINT_1227_0010_0001_dal_orchestrator.md | 80 + ...RINT_1227_0010_0002_dal_evidence_locker.md | 69 + ...SPRINT_1227_0010_0003_dal_export_center.md | 69 + ...INT_1227_0010_0004_dal_timeline_indexer.md | 69 + .../SPRINT_1227_0011_0001_dal_binary_index.md | 77 + .../SPRINT_1227_0011_0002_dal_signer.md | 56 + .../SPRINT_1227_0011_0003_dal_attestor.md | 82 + ...DOCS_module_documentation_consolidation.md | 329 + ...NT_1227_0005_0001_FE_diff_first_default.md | 268 + ...227_0005_0002_FE_proof_tree_integration.md | 388 + ...INT_1227_0005_0003_FE_copy_audit_export.md | 427 + ...SPRINT_1227_0005_0004_BE_verdict_replay.md | 515 + ...00_ADVISORY_binary_backport_fingerprint.md | 260 + ..._1227_0001_0001_LB_binary_vex_generator.md | 214 + ...SPRINT_1227_0001_0002_BE_resolution_api.md | 373 + ...1227_0002_0001_LB_reproducible_builders.md | 425 + .../SPRINT_1227_0003_0001_FE_backport_ui.md | 339 + ...227_0004_0001_BE_signature_verification.md | 351 + .../SPRINT_1227_0004_0002_FE_trust_column.md | 455 + .../SPRINT_1227_0004_0003_BE_vextrust_gate.md | 482 + ...NT_1227_0004_0004_LB_trust_attestations.md | 550 + ...T_1227_0004_ADVISORY_vex_trust_verifier.md | 275 + ...0005_ADVISORY_evidence_first_dashboards.md | 252 + ...227_0004_0001_BE_signature_verification.md | 348 + .../SPRINT_1227_0004_0003_BE_vextrust_gate.md | 480 + ...NT_1227_0004_0004_LB_trust_attestations.md | 548 + ...PRINT_1227_0012_0001_LB_reachgraph_core.md | 693 + ...RINT_1227_0012_0002_BE_reachgraph_store.md | 549 + ...227_0012_0003_FE_reachgraph_integration.md | 693 + ...SPRINT_1227_0013_0001_LB_cyclonedx_cbom.md | 249 + ...1227_0013_0002_LB_cvss_v4_environmental.md | 192 + ...014_0001_BE_stellaverdict_consolidation.md | 396 + .../SPRINT_1227_0014_0002_FE_verdict_ui.md | 284 + ...PRINT_20251226_015_AI_zastava_companion.md | 85 + ...SPRINT_20251226_016_AI_remedy_autopilot.md | 91 + .../SPRINT_20251226_017_AI_policy_copilot.md | 88 + .../SPRINT_20251226_018_AI_attestations.md | 87 + ...PRINT_20251226_019_AI_offline_inference.md | 104 + .../SPRINT_20251226_020_FE_ai_ux_patterns.md | 265 + ...RINT_20251228_001_BE_replay_manifest_ci.md | 241 + ..._20251228_002_BE_oci_attestation_attach.md | 314 + ...NT_20251228_003_FE_evidence_subgraph_ui.md | 386 + ...NT_20251228_004_AG_ebpf_runtime_signals.md | 439 + ...NT_20251228_005_BE_sbom_lineage_graph_i.md | 62 + ...NT_20251228_006_FE_sbom_lineage_graph_i.md | 66 + ...T_20251228_007_BE_sbom_lineage_graph_ii.md | 63 + ...T_20251228_008_FE_sbom_lineage_graph_ii.md | 78 + docs/modules/README.md | 123 + docs/modules/airgap/architecture.md | 348 + docs/modules/aoc/README.md | 37 + docs/modules/aoc/architecture.md | 126 + docs/modules/api/README.md | 48 + docs/modules/bench/README.md | 37 + docs/modules/benchmark/architecture.md | 2 + docs/modules/cryptography/architecture.md | 298 + docs/modules/evidence-locker/architecture.md | 278 + docs/modules/feedser/architecture.md | 237 + docs/modules/mirror/architecture.md | 60 + docs/modules/notifier/README.md | 38 + docs/modules/packsregistry/architecture.md | 100 + docs/modules/provenance/architecture.md | 316 + docs/modules/reachgraph/architecture.md | 231 + docs/modules/replay/architecture.md | 265 + docs/modules/riskengine/architecture.md | 325 + .../sbomservice/lineage/architecture.md | 469 + docs/modules/sbomservice/lineage/schema.sql | 319 + .../scanner/design/surface-env-release.md | 11 +- docs/modules/symbols/architecture.md | 71 + docs/modules/timelineindexer/architecture.md | 74 + docs/modules/ui/README.md | 6 + docs/modules/unknowns/architecture.md | 70 + docs/modules/web/README.md | 2 + docs/modules/web/architecture.md | 105 + ...dence_first_container_security_analysis.md | 271 + ...Building a Deterministic Verdict Engine.md | 0 ...cing Canonical JSON for Stable Verdicts.md | 0 ...olving Evidence Models for Reachability.md | 0 ...- Planning Keyless Signing for Verdicts.md | 0 ...stant as Proof-Carrying Evidence Engine.md | 0 .../26-Dec-2025 - AI Surfacing UX Patterns.md | 0 ...- SBOM Spine and Deterministic Evidence.md | 0 ...ext - SBOM Spine and Deterministic Evidence.md | 0 ...6 - Mapping a Binary Intelligence Graph.md | 0 ...ns Gap Analysis and Implementation Plan.md | 524 + .../archived/ADVISORY_SBOM_LINEAGE_GRAPH.md | 636 + ...istic Evidence and Verdict Architecture.md | 0 ...ff-Aware Release Gates and Risk Budgets.md | 0 docs/replay/replay-manifest-guide.md | 455 + docs/router/ARCHITECTURE.md | 402 + docs/router/GETTING_STARTED.md | 370 + docs/router/README.md | 294 + .../Examples.Integration.Tests.csproj | 6 +- docs/router/transports/README.md | 203 + docs/router/transports/development.md | 534 + docs/router/transports/inmemory.md | 233 + docs/router/transports/rabbitmq.md | 241 + docs/router/transports/tcp.md | 135 + docs/router/transports/tls.md | 223 + docs/router/transports/udp.md | 173 + docs/schemas/plugin-config.schema.json | 48 + docs/schemas/plugin-manifest.schema.json | 157 + docs/schemas/plugin-registry.schema.json | 102 + docs/sdks/overview.md | 63 +- docs/sdks/plugin-development.md | 580 + docs/testing/LOCAL_CI_GUIDE.md | 428 + docs/testing/PRE_COMMIT_CHECKLIST.md | 164 + docs/testing/README.md | 168 + etc/policy-gates.yaml.sample | 63 + fix-asynclifetime.ps1 | 10 + fix-fluentassertions.ps1 | 10 + fix-npgsql.ps1 | 8 + fix-xunit-refs.ps1 | 17 + policies/schemas/policy-pack.schema.json | 29 + policies/starter-day1.yaml | 6 + scripts/fix-duplicate-packages.ps1 | 65 + src/AdvisoryAI/AGENTS.md | 2 +- .../StellaOps.AdvisoryAI.Hosting.csproj | 4 + .../Program.cs | 8 +- .../Properties/launchSettings.json | 12 + .../StellaOps.AdvisoryAI.WebService.csproj | 4 +- .../Services/AdvisoryTaskWorker.cs | 5 +- .../StellaOps.AdvisoryAI.Worker.csproj | 4 + src/AdvisoryAI/StellaOps.AdvisoryAI.sln | 705 +- .../ProviderBasedAdvisoryInferenceClient.cs | 2 + .../StellaOps.AdvisoryAI.csproj | 6 +- .../AdvisoryGuardrailInjectionTests.cs | 3 +- .../AdvisoryGuardrailOptionsBindingTests.cs | 3 +- .../AdvisoryGuardrailPerformanceTests.cs | 3 +- .../AdvisoryPipelineExecutorTests.cs | 3 +- .../AdvisoryPromptAssemblerTests.cs | 5 +- .../ExplanationGeneratorIntegrationTests.cs | 2 +- .../StellaOps.AdvisoryAI.Tests.csproj | 16 +- .../StellaOps.AirGap.Controller/Program.cs | 3 +- .../Properties/launchSettings.json | 12 + .../StellaOps.AirGap.Importer.csproj | 8 +- ...tellaOps.AirGap.Importer.csproj.Backup.tmp | 21 + .../HttpClientUsageAnalyzerTests.cs | 3 +- .../PolicyAnalyzerRoslynTests.cs | 3 +- ...laOps.AirGap.Policy.Analyzers.Tests.csproj | 13 +- .../StellaOps.AirGap.Policy.Analyzers.csproj | 4 +- .../EgressPolicyTests.cs | 3 +- .../StellaOps.AirGap.Policy.Tests.csproj | 3 +- .../StellaOps.AirGap.Policy.csproj | 6 +- .../StellaOps.AirGap.Policy.csproj.Backup.tmp | 15 + .../AirGapPostgresFixture.cs | 30 - ...laOps.AirGap.Storage.Postgres.Tests.csproj | 34 - .../StellaOps.AirGap.Storage.Postgres.csproj | 13 - .../Properties/launchSettings.json | 12 + .../StellaOps.AirGap.Time.csproj | 2 +- src/AirGap/StellaOps.AirGap.sln | 450 + .../StellaOps.AirGap.Bundle.csproj | 6 +- .../EfCore/Context/AirGapDbContext.cs | 21 + .../AirGapPersistenceExtensions.cs} | 24 +- .../Postgres}/AirGapDataSource.cs | 2 +- .../Repositories/PostgresAirGapStateStore.cs | 2 +- .../PostgresBundleVersionStore.cs | 2 +- .../StellaOps.AirGap.Persistence.csproj | 27 + .../AirGapIntegrationTests.cs | 35 +- .../BundleDeterminismTests.cs | 4 +- .../BundleExportImportTests.cs | 32 +- .../BundleImportTests.cs | 1 - .../StellaOps.AirGap.Bundle.Tests.csproj | 14 +- ...GapStartupDiagnosticsHostedServiceTests.cs | 1 + .../AirGapStateServiceTests.cs | 1 + .../InMemoryAirGapStateStoreTests.cs | 1 + .../ReplayVerificationServiceTests.cs | 1 + .../StellaOps.AirGap.Controller.Tests.csproj | 13 + .../AirGapControllerContractTests.cs | 3 +- .../BundleImportPlannerTests.cs | 0 .../DsseVerifierTests.cs | 1 - .../GlobalUsings.cs | 0 .../ImportValidatorTests.cs | 1 - .../InMemoryBundleRepositoriesTests.cs | 0 .../MerkleRootCalculatorTests.cs | 0 .../OfflineKitMetricsTests.cs | 3 +- .../FileSystemQuarantineServiceTests.cs | Bin 0 -> 12660 bytes .../Reconciliation/ArtifactIndexTests.cs | Bin 0 -> 4180 bytes .../Reconciliation/CycloneDxParserTests.cs | Bin 0 -> 9080 bytes .../DsseAttestationParserTests.cs | Bin 0 -> 9528 bytes .../EvidenceDirectoryDiscoveryTests.cs | Bin 0 -> 5044 bytes .../Reconciliation/Fixtures/sample.cdx.json | Bin 0 -> 2696 bytes .../Fixtures/sample.intoto.json | Bin 0 -> 1346 bytes .../Reconciliation/Fixtures/sample.spdx.json | Bin 0 -> 4824 bytes .../SourcePrecedenceLatticePropertyTests.cs | Bin 0 -> 28164 bytes .../Reconciliation/SpdxParserTests.cs | Bin 0 -> 9482 bytes .../ReplayVerifierTests.cs | Bin 0 -> 4608 bytes .../RootRotationPolicyTests.cs | Bin 0 -> 3148 bytes .../StellaOps.AirGap.Importer.Tests.csproj | 20 +- .../TufMetadataValidatorTests.cs | Bin 0 -> 3714 bytes .../ImportValidatorIntegrationTests.cs | Bin 0 -> 16230 bytes .../RekorOfflineReceiptVerifierTests.cs | Bin 0 -> 14122 bytes .../Versioning/BundleVersionTests.cs | Bin 0 -> 5480 bytes .../VersionMonotonicityCheckerTests.cs | Bin 0 -> 12862 bytes .../AirGapPostgresFixture.cs | 127 + .../AirGapStorageIntegrationTests.cs | 40 +- .../PostgresAirGapStateStoreTests.cs | 6 +- .../StellaOps.AirGap.Persistence.Tests.csproj | 24 + .../AirGapOptionsValidatorTests.cs | 0 .../GlobalUsings.cs | 0 .../Rfc3161VerifierTests.cs | 0 .../RoughtimeVerifierTests.cs | 0 .../SealedStartupValidatorTests.cs | 0 .../StalenessCalculatorTests.cs | 0 .../StellaOps.AirGap.Time.Tests.csproj | 9 +- .../TimeAnchorLoaderTests.cs | 0 .../TimeAnchorPolicyServiceTests.cs | 4 +- .../TimeStatusDtoTests.cs | 0 .../TimeStatusServiceTests.cs | 0 .../TimeTelemetryTests.cs | 0 .../TimeTokenParserTests.cs | 0 .../TimeVerificationServiceTests.cs | 0 src/Aoc/StellaOps.Aoc.sln | 163 +- .../StellaOps.Aoc.Analyzers.csproj | 4 +- .../StellaOps.Aoc/StellaOps.Aoc.csproj | 4 +- .../AocForbiddenFieldAnalyzerTests.cs | 2 +- .../StellaOps.Aoc.Analyzers.Tests.csproj | 22 +- .../AocGuardEndpointFilterExtensionsTests.cs | 3 +- .../AocHttpResultsTests.cs | 5 +- .../StellaOps.Aoc.AspNetCore.Tests.csproj | 13 +- .../StellaOps.Aoc.Tests/AocWriteGuardTests.cs | 3 +- .../StellaOps.Aoc.Tests.csproj | 9 +- .../DsseHelperTests.cs | 3 + .../StellaOps.Attestation.Tests.csproj | 7 +- .../DsseCosignCompatibilityTestFixture.cs | 352 - .../DsseCosignCompatibilityTests.cs | 423 - .../DsseEnvelopeSerializerTests.cs | 61 - .../DsseNegativeTests.cs | 354 - .../DsseRebundleTests.cs | 364 - .../DsseRoundtripTestFixture.cs | 503 - .../DsseRoundtripTests.cs | 381 - .../EnvelopeSignatureServiceTests.cs | 159 - .../StellaOps.Attestor.Envelope.Tests.csproj | 23 - .../StellaOps.Attestor.Envelope.csproj | 2 +- .../DsseEnvelopeSerializerTests.cs | 3 +- .../StellaOps.Attestor.Envelope.Tests.csproj | 20 +- src/Attestor/StellaOps.Attestor.sln | 917 +- .../StellaOps.Attestor.Core.Tests.csproj | 24 +- .../Delta/DeltaAttestationService.cs | 325 + .../Delta/IDeltaAttestationService.cs | 184 + .../Schemas/sbom-delta.v1.schema.json | 116 + .../Schemas/verdict-delta.v1.schema.json | 129 + .../Schemas/vex-delta.v1.schema.json | 98 + .../StellaOps.Attestor.Core.csproj | 9 +- .../Validation/PredicateSchemaValidator.cs | 14 +- ...1216_001_create_rekor_submission_queue.sql | 0 .../Migrations/_archived/pre_1.0/README.md | 21 + .../ServiceCollectionExtensions.cs | 2 + .../StellaOps.Attestor.Infrastructure.csproj | 20 +- ....Attestor.Infrastructure.csproj.Backup.tmp | 29 + .../AttestationBundleEndpointsTests.cs | 2 +- .../AttestorSigningServiceTests.cs | 3 +- .../AttestorSubmissionServiceTests.cs | 3 +- .../AttestorVerificationServiceTests.cs | 3 +- .../Auth/AttestorAuthTests.cs | 1 - .../BulkVerificationWorkerTests.cs | 3 +- .../CachedAttestorVerificationServiceTests.cs | 3 +- .../Contract/AttestorContractSnapshotTests.cs | 1 - .../HttpTransparencyWitnessClientTests.cs | 3 +- ...resRekorSubmissionQueueIntegrationTests.cs | 12 +- .../Negative/AttestorNegativeTests.cs | 1 - .../Observability/AttestorOTelTraceTests.cs | 1 - ...orInclusionVerificationIntegrationTests.cs | 3 +- .../StellaOps.Attestor.Tests.csproj | 30 +- .../Controllers/ProofChainController.cs | 2 +- .../Properties/launchSettings.json | 12 + .../Services/PredicateTypeRouter.cs | 8 +- .../Services/ProofChainQueryService.cs | 11 +- .../StellaOps.Attestor.WebService.csproj | 18 +- .../StellaOps.Attestor/StellaOps.Attestor.sln | 132 - .../StellaOps.Attestor.Bundle.csproj | 4 +- .../StellaOps.Attestor.Bundling.csproj | 6 +- ...llaOps.Attestor.Bundling.csproj.Backup.tmp | 24 + .../StellaOps.Attestor.GraphRoot.csproj | 6 +- ...laOps.Attestor.GraphRoot.csproj.Backup.tmp | 27 + .../Services/IOciAttestationAttacher.cs | 299 + .../Services/IOciRegistryClient.cs | 186 + .../Services/OrasAttestationAttacher.cs | 405 + .../StellaOps.Attestor.Oci.csproj | 19 + .../Services/FileSystemRootStore.cs | 2 +- .../Services/OfflineVerifier.cs | 4 +- .../StellaOps.Attestor.Offline.csproj | 6 +- ...ellaOps.Attestor.Offline.csproj.Backup.tmp | 26 + .../Migrations/001_initial_schema.sql | 245 + .../20251214000001_AddProofChainSchema.sql | 0 ...0251214000002_RollbackProofChainSchema.sql | 0 .../Migrations/_archived/pre_1.0/README.md | 22 + .../StellaOps.Attestor.Persistence.csproj | 4 +- .../Json/IJsonSchemaValidator.cs | 71 + .../Json/Rfc8785JsonCanonicalizer.cs | 4 +- .../Predicates/SbomDeltaPredicate.cs | 239 + .../Predicates/VerdictDeltaPredicate.cs | 287 + .../Predicates/VexDeltaPredicate.cs | 203 + .../StellaOps.Attestor.ProofChain.csproj | 4 +- ...ellaOps.Attestor.StandardPredicates.csproj | 5 +- ...ellaOps.Attestor.TrustVerdict.Tests.csproj | 24 + .../TrustEvidenceMerkleBuilderTests.cs | 355 + .../TrustVerdictCacheTests.cs | 300 + .../TrustVerdictServiceTests.cs | 487 + .../Caching/TrustVerdictCache.cs | 559 + .../Evidence/TrustEvidenceMerkleBuilder.cs | 367 + .../JsonCanonicalizer.cs | 202 + .../Migrations/001_create_trust_verdicts.sql | 135 + .../Oci/TrustVerdictOciAttacher.cs | 398 + .../Persistence/TrustVerdictRepository.cs | 622 + .../Predicates/TrustVerdictPredicate.cs | 501 + .../Services/TrustVerdictService.cs | 642 + .../StellaOps.Attestor.TrustVerdict.csproj | 27 + .../Telemetry/TrustVerdictMetrics.cs | 298 + ...TrustVerdictServiceCollectionExtensions.cs | 142 + .../StellaOps.Attestor.GraphRoot.Tests.csproj | 25 +- .../SigstoreBundleVerifierTests.cs | 3 +- .../StellaOps.Attestor.Bundle.Tests.csproj | 13 +- .../BundleWorkflowIntegrationTests.cs | 5 +- .../StellaOps.Attestor.Bundling.Tests.csproj | 19 +- .../OciAttestationAttacherIntegrationTests.cs | 131 + .../OciReferenceTests.cs | 140 + .../OrasAttestationAttacherTests.cs | 213 + .../StellaOps.Attestor.Oci.Tests.csproj | 31 + .../FileSystemRootStoreTests.cs | 3 +- .../OfflineCertChainValidatorTests.cs | 3 +- .../StellaOps.Attestor.Offline.Tests.csproj | 19 +- ...tellaOps.Attestor.Persistence.Tests.csproj | 26 +- .../Envelope/DsseEnvelopeDeterminismTests.cs | 10 +- .../JsonCanonicalizerTests.cs | 5 +- ...StellaOps.Attestor.ProofChain.Tests.csproj | 24 +- ...s.Attestor.StandardPredicates.Tests.csproj | 17 +- .../TrustVerdictIntegrationTests.cs | 742 + .../AttestationGoldenSamplesTests.cs | 2 +- .../AttestationDeterminismTests.cs | 1 - ...omAttestationSignVerifyIntegrationTests.cs | 1 - .../Rekor/RekorInclusionProofTests.cs | 3 +- .../Rekor/RekorReceiptGenerationTests.cs | 3 +- .../Rekor/RekorReceiptVerificationTests.cs | 1 - .../SmartDiffSchemaValidationTests.cs | 3 +- .../StellaOps.Attestor.Types.Tests.csproj | 24 +- src/Authority/StellaOps.Authority.sln | 806 +- .../StellaOps.Auth.Abstractions.Tests.csproj | 2 +- .../StellaOps.Auth.Abstractions.csproj | 4 +- .../ServiceCollectionExtensionsTests.cs | 3 +- .../StellaOps.Auth.Client.Tests.csproj | 6 +- .../StellaOpsTokenClientTests.cs | 1 + .../StellaOps.Auth.Client.csproj | 8 +- .../bin2/StellaOps.Auth.Abstractions.xml | 877 - .../bin2/StellaOps.Auth.Client.deps.json | 410 - .../ServiceCollectionExtensionsTests.cs | 3 +- ...llaOps.Auth.ServerIntegration.Tests.csproj | 2 +- ...StellaOpsScopeAuthorizationHandlerTests.cs | 6 +- .../StellaOps.Auth.ServerIntegration.csproj | 6 +- .../LdapClientProvisioningStoreTests.cs | 6 +- .../Credentials/LdapCredentialStoreTests.cs | 6 +- ...ellaOps.Authority.Plugin.Ldap.Tests.csproj | 14 +- .../TestHelpers/TestAirgapAuditStore.cs | 6 +- .../LdapClientProvisioningStore.cs | 4 +- .../Credentials/LdapCredentialStore.cs | 4 +- .../LdapPluginRegistrar.cs | 2 +- .../StellaOps.Authority.Plugin.Ldap.csproj | 13 +- .../Security/OidcConnectorSecurityTests.cs | 8 +- ...ellaOps.Authority.Plugin.Oidc.Tests.csproj | 14 +- .../StellaOps.Authority.Plugin.Oidc.csproj | 12 +- ...ps.Authority.Plugin.Oidc.csproj.Backup.tmp | 25 + .../SamlConnectorResilienceTests.cs | 4 +- .../Security/SamlConnectorSecurityTests.cs | 4 +- ...ellaOps.Authority.Plugin.Saml.Tests.csproj | 14 +- .../StellaOps.Authority.Plugin.Saml.csproj | 10 +- ...ps.Authority.Plugin.Saml.csproj.Backup.tmp | 24 + .../StandardClientProvisioningStoreTests.cs | 10 +- .../StandardPluginRegistrarTests.cs | 12 +- .../StandardUserCredentialStoreTests.cs | 7 +- ...Ops.Authority.Plugin.Standard.Tests.csproj | 6 +- .../StandardPluginRegistrar.cs | 4 +- ...StellaOps.Authority.Plugin.Standard.csproj | 11 +- .../StandardClientProvisioningStore.cs | 4 +- .../Storage/StandardUserCredentialStore.cs | 4 +- ...uthority.Plugins.Abstractions.Tests.csproj | 4 +- ...aOps.Authority.Plugins.Abstractions.csproj | 6 +- ...ity.Plugins.Abstractions.csproj.Backup.tmp | 27 + .../AdvisoryAiRemoteInferenceEndpointTests.cs | 6 +- .../Airgap/AirgapAuditEndpointsTests.cs | 6 +- .../Audit/AuthorityAuditSinkTests.cs | 8 +- .../Auth/AuthorityAuthBypassTests.cs | 1 - .../BootstrapInviteCleanupServiceTests.cs | 6 +- .../ServiceAccountAdminEndpointsTests.cs | 16 +- .../AuthorityContractSnapshotTests.cs | 1 - .../Errors/KeyErrorClassificationTests.cs | 1 - .../AuthorityIdentityProviderRegistryTests.cs | 8 +- .../AuthorityWebApplicationFactory.cs | 8 +- .../Infrastructure/TestAirgapAuditStore.cs | 6 +- .../Negative/AuthorityNegativeTests.cs | 1 - .../AuthorityAckTokenIssuerTests.cs | 8 +- .../Observability/AuthorityOTelTraceTests.cs | 1 - .../ClientCredentialsAndTokenHandlersTests.cs | 6 +- .../OpenIddict/PasswordGrantHandlersTests.cs | 6 +- .../TokenPersistenceIntegrationTests.cs | 6 +- .../Permalinks/VulnPermalinkServiceTests.cs | 2 +- .../Signing/TokenSignVerifyRoundtripTests.cs | 25 +- .../StellaOps.Authority.Tests.csproj | 23 +- .../StellaOps.Authority.sln | 398 - .../Airgap/AuthorityAirgapAuditService.cs | 10 +- .../Audit/AuthorityAuditSink.cs | 4 +- .../BootstrapInviteCleanupService.cs | 4 +- .../Admin/ConsoleAdminEndpointExtensions.cs | 2 +- .../IncidentAuditEndpointExtensions.cs | 4 +- .../Handlers/ClientCredentialsHandlers.cs | 8 +- .../OpenIddict/Handlers/DpopHandlers.cs | 4 +- .../Handlers/PasswordGrantHandlers.cs | 4 +- .../Handlers/RefreshTokenHandlers.cs | 4 +- .../OpenIddict/Handlers/RevocationHandlers.cs | 4 +- .../Handlers/TokenPersistenceHandlers.cs | 14 +- .../Handlers/TokenValidationHandlers.cs | 8 +- .../StellaOps.Authority/Program.Partial.cs | 2 +- .../StellaOps.Authority/Program.cs | 19 +- .../Revocation/RevocationBundleBuilder.cs | 4 +- ...horityClientCertificateValidationResult.cs | 2 +- .../AuthorityClientCertificateValidator.cs | 2 +- .../IAuthorityClientCertificateValidator.cs | 2 +- .../StellaOps.Authority.csproj | 25 +- .../Postgres/PostgresAirgapAuditStore.cs | 12 +- .../Postgres/PostgresBootstrapInviteStore.cs | 12 +- .../Storage/Postgres/PostgresClientStore.cs | 12 +- .../Postgres/PostgresLoginAttemptStore.cs | 12 +- .../PostgresRevocationExportStateStore.cs | 12 +- .../Postgres/PostgresRevocationStore.cs | 12 +- .../Postgres/PostgresServiceAccountStore.cs | 12 +- .../Storage/Postgres/PostgresTokenStore.cs | 20 +- .../StellaOps.Authority.Core.csproj | 2 - .../EfCore/Context/AuthorityDbContext.cs | 21 + .../AuthorityPersistenceExtensions.cs | 83 + .../InMemory}/Documents/AuthorityDocuments.cs | 6 +- .../InMemory}/Documents/TokenUsage.cs | 2 +- .../InMemory}/Driver/InMemoryDriverShim.cs | 0 .../Extensions/ServiceCollectionExtensions.cs | 10 +- .../AuthorityStorageInitializer.cs | 2 +- .../Serialization/SerializationAttributes.cs | 0 .../Serialization/SerializationTypes.cs | 0 .../Sessions/IClientSessionHandle.cs | 2 +- .../InMemory}/Stores/IAuthorityStores.cs | 6 +- .../InMemory}/Stores/InMemoryStores.cs | 6 +- .../Migrations/001_initial_schema.sql | 609 + .../_archived/pre_1.0}/001_initial_schema.sql | 0 .../pre_1.0}/002_mongo_store_equivalents.sql | 0 .../_archived/pre_1.0}/003_enable_rls.sql | 0 .../pre_1.0}/004_offline_kit_audit.sql | 0 .../pre_1.0}/005_verdict_manifests.sql | 0 .../Postgres}/AuthorityDataSource.cs | 2 +- .../Postgres}/Models/AirgapAuditEntity.cs | 2 +- .../Postgres}/Models/BootstrapInviteEntity.cs | 2 +- .../Postgres}/Models/ClientEntity.cs | 4 +- .../Postgres}/Models/LoginAttemptEntity.cs | 2 +- .../Postgres}/Models/OfflineKitAuditEntity.cs | 2 +- .../Postgres}/Models/OidcTokenEntity.cs | 2 +- .../Postgres}/Models/RevocationEntity.cs | 2 +- .../Models/RevocationExportStateEntity.cs | 2 +- .../Postgres}/Models/RoleEntity.cs | 2 +- .../Postgres}/Models/ServiceAccountEntity.cs | 2 +- .../Postgres}/Models/SessionEntity.cs | 2 +- .../Postgres}/Models/TenantEntity.cs | 2 +- .../Postgres}/Models/TokenEntity.cs | 2 +- .../Postgres}/Models/UserEntity.cs | 2 +- .../Repositories/AirgapAuditRepository.cs | 4 +- .../Repositories/ApiKeyRepository.cs | 4 +- .../Postgres}/Repositories/AuditRepository.cs | 4 +- .../Repositories/BootstrapInviteRepository.cs | 4 +- .../Repositories/ClientRepository.cs | 4 +- .../Repositories/IApiKeyRepository.cs | 4 +- .../Repositories/IAuditRepository.cs | 4 +- .../Repositories/IOfflineKitAuditEmitter.cs | 4 +- .../IOfflineKitAuditRepository.cs | 4 +- .../Repositories/IPermissionRepository.cs | 4 +- .../Postgres}/Repositories/IRoleRepository.cs | 4 +- .../Repositories/ISessionRepository.cs | 4 +- .../Repositories/ITenantRepository.cs | 4 +- .../Repositories/ITokenRepository.cs | 4 +- .../Postgres}/Repositories/IUserRepository.cs | 4 +- .../Repositories/LoginAttemptRepository.cs | 4 +- .../Repositories/OfflineKitAuditEmitter.cs | 4 +- .../Repositories/OfflineKitAuditRepository.cs | 4 +- .../Repositories/OidcTokenRepository.cs | 4 +- .../Repositories/PermissionRepository.cs | 4 +- .../RevocationExportStateRepository.cs | 4 +- .../Repositories/RevocationRepository.cs | 4 +- .../Postgres}/Repositories/RoleRepository.cs | 4 +- .../Repositories/ServiceAccountRepository.cs | 4 +- .../Repositories/SessionRepository.cs | 4 +- .../Repositories/TenantRepository.cs | 4 +- .../Postgres}/Repositories/TokenRepository.cs | 4 +- .../Postgres}/Repositories/UserRepository.cs | 4 +- .../Postgres}/ServiceCollectionExtensions.cs | 4 +- .../Postgres}/VerdictManifestStore.cs | 2 +- .../StellaOps.Authority.Persistence.csproj | 34 + .../AGENTS.md | 24 - ...tellaOps.Authority.Storage.Postgres.csproj | 22 - .../StellaOps.Authority.Core.Tests.csproj | 14 +- .../ApiKeyConcurrencyTests.cs | 8 +- .../ApiKeyIdempotencyTests.cs | 27 +- .../ApiKeyRepositoryTests.cs | 10 +- .../AuditRepositoryTests.cs | 10 +- .../AuthorityMigrationTests.cs | 5 +- .../AuthorityPostgresFixture.cs | 4 +- .../OfflineKitAuditRepositoryTests.cs | 10 +- .../PermissionRepositoryTests.cs | 10 +- .../RefreshTokenRepositoryTests.cs | 10 +- .../RoleBasedAccessTests.cs | 8 +- .../RoleRepositoryTests.cs | 10 +- .../SessionRepositoryTests.cs | 10 +- ...ellaOps.Authority.Persistence.Tests.csproj | 26 + .../InMemoryAuthorityRepositories.cs | 6 +- .../TokenRepositoryTests.cs | 10 +- ...ps.Authority.Storage.Postgres.Tests.csproj | 36 - src/Bench/StellaOps.Bench.sln | 1103 +- ...llaOps.Bench.LinkNotMerge.Vex.Tests.csproj | 9 +- .../StellaOps.Bench.LinkNotMerge.Vex.csproj | 2 +- .../StellaOps.Bench.LinkNotMerge.Tests.csproj | 9 +- .../StellaOps.Bench.LinkNotMerge.csproj | 2 +- .../StellaOps.Bench.Notify.Tests.csproj | 9 +- .../StellaOps.Bench.Notify.csproj | 2 +- .../PolicyScenarioRunner.cs | 2 +- .../StellaOps.Bench.PolicyEngine.csproj | 2 +- .../BenchmarkJsonWriterTests.cs | 3 +- ...llaOps.Bench.ScannerAnalyzers.Tests.csproj | 15 +- .../Controllers/ResolutionController.cs | 176 + .../Middleware/RateLimitingMiddleware.cs | 223 + .../Program.cs | 49 + .../Properties/launchSettings.json | 12 + .../StellaOps.BinaryIndex.WebService.csproj | 25 + .../Telemetry/ResolutionTelemetry.cs | 218 + .../appsettings.Development.json | 8 + .../appsettings.json | 33 + .../Jobs/ReproducibleBuildJob.cs | 308 + src/BinaryIndex/StellaOps.BinaryIndex.sln | 407 + .../BuilderOptions.cs | 175 + .../FingerprintClaimModels.cs | 304 + .../IFunctionFingerprintExtractor.cs | 220 + .../IPatchDiffEngine.cs | 216 + .../IReproducibleBuilder.cs | 428 + .../PatchDiffEngine.cs | 288 + .../ReproducibleBuildJobTypes.cs | 371 + .../ServiceCollectionExtensions.cs | 62 + .../StellaOps.BinaryIndex.Builders.csproj | 24 + .../CachedBinaryVulnerabilityService.cs | 14 +- .../ResolutionCacheService.cs | 279 + .../StellaOps.BinaryIndex.Cache.csproj | 13 +- .../Resolution/VulnResolutionContracts.cs | 186 + .../StellaOps.BinaryIndex.Contracts.csproj} | 8 +- .../Resolution/ResolutionService.cs | 360 + .../Services/IBinaryVulnerabilityService.cs | 4 + .../StellaOps.BinaryIndex.Core.csproj | 8 +- .../AlpinePackageExtractor.cs | 4 +- ...StellaOps.BinaryIndex.Corpus.Alpine.csproj | 6 +- ...StellaOps.BinaryIndex.Corpus.Debian.csproj | 6 +- .../RpmPackageExtractor.cs | 6 +- .../StellaOps.BinaryIndex.Corpus.Rpm.csproj | 6 +- .../StellaOps.BinaryIndex.Corpus.csproj | 2 +- .../StellaOps.BinaryIndex.Fingerprints.csproj | 2 +- .../Parsers/AlpineSecfixesParser.cs | 3 +- .../Parsers/DebianChangelogParser.cs | 3 +- .../Parsers/PatchHeaderParser.cs | 3 +- .../Parsers/RpmChangelogParser.cs | 6 +- .../StellaOps.BinaryIndex.FixIndex.csproj | 2 +- .../Migrations/001_initial_schema.sql | 432 + .../Migrations/002_fingerprint_claims.sql | 251 + .../pre_1.0}/001_create_binaries_schema.sql | 0 .../002_create_fingerprint_tables.sql | 0 .../pre_1.0}/003_create_fix_index_tables.sql | 0 .../20251226_AddFingerprintTables.sql | 0 .../Repositories/CorpusSnapshotRepository.cs | 24 +- .../StellaOps.BinaryIndex.Persistence.csproj | 6 +- .../BinaryMatchEvidenceSchema.cs | 122 + .../IDsseSigningAdapter.cs | 59 + .../IVexEvidenceGenerator.cs | 97 + .../ServiceCollectionExtensions.cs | 50 + .../StellaOps.BinaryIndex.VexBridge.csproj | 24 + .../VexBridgeOptions.cs | 54 + .../VexEvidenceGenerator.cs | 468 + .../ReproducibleBuildJobIntegrationTests.cs | 627 + ...tellaOps.BinaryIndex.Builders.Tests.csproj | 24 + .../FeatureExtractorTests.cs | 3 +- .../FixIndexBuilderIntegrationTests.cs | 3 +- .../FixIndex/ParserTests.cs | 1 + .../StellaOps.BinaryIndex.Core.Tests.csproj | 14 +- .../Matching/FingerprintMatcherTests.cs | 2 +- ...aOps.BinaryIndex.Fingerprints.Tests.csproj | 12 +- .../BinaryIdentityRepositoryTests.cs | 2 +- .../CorpusSnapshotRepositoryTests.cs | 2 +- ...laOps.BinaryIndex.Persistence.Tests.csproj | 6 +- ...ellaOps.BinaryIndex.VexBridge.Tests.csproj | 29 + .../VexBridgeIntegrationTests.cs | 281 + .../VexEvidenceGeneratorTests.cs | 459 + .../ResolutionControllerIntegrationTests.cs | 407 + src/Cartographer/StellaOps.Cartographer.sln | 767 +- .../Properties/launchSettings.json | 12 + .../StellaOps.Cartographer.csproj | 2 +- .../StellaOps.Cartographer.Tests.csproj | 6 - src/Cli/StellaOps.Cli.sln | 1328 +- .../Commands/AttestCommandGroup.cs | 564 + .../Commands/Budget/RiskBudgetCommandGroup.cs | 46 +- .../StellaOps.Cli/Commands/CliExitCodes.cs | 52 + .../StellaOps.Cli/Commands/CommandFactory.cs | 249 + .../Commands/CommandHandlers.Feeds.cs | 13 - .../Commands/CommandHandlers.Model.cs | 13 - .../StellaOps.Cli/Commands/CommandHandlers.cs | 319 +- .../Commands/FeedsCommandGroup.cs | 12 +- .../Proof/FuncProofCommandHandlers.cs | 13 +- .../ReachGraph/ReachGraphCommandGroup.cs | 179 + .../ReachGraph/ReachGraphCommandHandlers.cs | 446 + .../Commands/ReplayCommandGroup.cs | 186 + .../Commands/SignCommandGroup.cs | 12 +- .../Configuration/StellaOpsCliOptions.cs | 26 + .../StellaOps.Cli/Output/IOutputRenderer.cs | 15 +- .../StellaOps.Cli/Output/OutputRenderer.cs | 9 +- .../Output/OutputRendererExtensions.cs | 75 + src/Cli/StellaOps.Cli/Program.cs | 2 +- .../Services/MigrationModuleRegistry.cs | 24 +- .../StellaOps.Cli/Services/Models/OciTypes.cs | 2 +- .../Services/VerdictAttestationVerifier.cs | 3 +- src/Cli/StellaOps.Cli/StellaOps.Cli.csproj | 35 +- src/Cli/StellaOps.Cli/Telemetry/CliMetrics.cs | 31 + .../StellaOps.Cli.Plugins.Aoc.csproj | 2 +- .../StellaOps.Cli.Plugins.Symbols.csproj | 2 +- .../StellaOps.Cli.Plugins.Verdict.csproj | 28 + .../VerdictCliCommandModule.cs | 652 + .../StellaOps.Cli.Plugins.Vex.csproj | 2 +- .../VexCliCommandModule.cs | 178 +- .../AttestationBundleVerifierTests.cs | 3 +- .../StellaOps.Cli.Tests/CryptoCommandTests.cs | 6 +- .../GoldenOutput/ScanCommandGoldenTests.cs | 2 +- .../StellaOps.Cli.Tests.csproj | 34 +- .../StellaOps.Cli/Commands/CliExitCodes.cs | 52 + .../Output/OutputRendererExtensions.cs | 65 + src/Concelier/Directory.Build.props | 27 - .../DualWrite/DualWriteAdvisoryStore.cs | 2 +- .../FeedSnapshotEndpointExtensions.cs | 2 + .../StellaOps.Concelier.WebService/Program.cs | 11 +- .../StellaOps.Concelier.WebService.csproj | 28 +- src/Concelier/StellaOps.Concelier.sln | 3642 ++- .../StellaOps.Concelier.Analyzers.csproj | 5 +- ...StellaOps.Concelier.Merge.Analyzers.csproj | 4 +- .../StellaOps.Concelier.Cache.Valkey.csproj | 16 +- .../StellaOps.Concelier.Connector.Acsc.csproj | 2 +- .../StellaOps.Concelier.Connector.Cccs.csproj | 2 +- ...llaOps.Concelier.Connector.CertBund.csproj | 2 +- .../CertCcConnector.cs | 15 +- ...tellaOps.Concelier.Connector.CertCc.csproj | 4 +- ...tellaOps.Concelier.Connector.CertFr.csproj | 2 +- ...tellaOps.Concelier.Connector.CertIn.csproj | 2 +- .../Json/JsonSchemaValidator.cs | 4 +- ...tellaOps.Concelier.Connector.Common.csproj | 12 +- .../Testing/CannedHttpMessageHandler.cs | 11 + .../StellaOps.Concelier.Connector.Cve.csproj | 2 +- ...s.Concelier.Connector.Distro.Debian.csproj | 2 +- ...s.Concelier.Connector.Distro.RedHat.csproj | 2 +- ...Ops.Concelier.Connector.Distro.Suse.csproj | 2 +- ...s.Concelier.Connector.Distro.Ubuntu.csproj | 2 +- .../StellaOps.Concelier.Connector.Epss.csproj | 1 + .../StellaOps.Concelier.Connector.Ghsa.csproj | 2 +- ...llaOps.Concelier.Connector.Ics.Cisa.csproj | 4 +- ...s.Concelier.Connector.Ics.Kaspersky.csproj | 2 +- .../StellaOps.Concelier.Connector.Jvn.csproj | 2 +- .../StellaOps.Concelier.Connector.Kev.csproj | 2 +- .../StellaOps.Concelier.Connector.Kisa.csproj | 2 +- .../StellaOps.Concelier.Connector.Nvd.csproj | 2 +- .../StellaOps.Concelier.Connector.Osv.csproj | 2 +- ...tellaOps.Concelier.Connector.Ru.Bdu.csproj | 2 +- ...llaOps.Concelier.Connector.Ru.Nkcki.csproj | 4 +- ...Concelier.Connector.StellaOpsMirror.csproj | 2 +- ...aOps.Concelier.Connector.Vndr.Adobe.csproj | 4 +- ...aOps.Concelier.Connector.Vndr.Apple.csproj | 2 +- ...s.Concelier.Connector.Vndr.Chromium.csproj | 6 +- ...aOps.Concelier.Connector.Vndr.Cisco.csproj | 2 +- ...laOps.Concelier.Connector.Vndr.Msrc.csproj | 2 +- .../Internal/OracleCursor.cs | 32 + .../OracleConnector.cs | 3 +- ...Ops.Concelier.Connector.Vndr.Oracle.csproj | 2 +- .../Internal/VmwareFetchCacheEntry.cs | 36 + ...Ops.Concelier.Connector.Vndr.Vmware.csproj | 2 +- .../VmwareConnector.cs | 3 +- .../StellaOps.Concelier.Core.csproj | 12 +- .../StellaOps.Concelier.Exporter.Json.csproj | 8 +- ...tellaOps.Concelier.Exporter.TrivyDb.csproj | 6 +- .../Export/BundleExportService.cs | 2 +- .../Export/DeltaQueryService.cs | 2 +- .../StellaOps.Concelier.Federation.csproj | 10 +- .../StellaOps.Concelier.Interest.csproj | 14 +- .../StellaOps.Concelier.Merge.csproj | 8 +- .../StellaOps.Concelier.Models.csproj | 2 +- .../StellaOps.Concelier.Normalization.csproj | 4 +- .../EfCore/Context/ConcelierDbContext.cs | 21 + .../ConcelierPersistenceExtensions.cs | 118 + .../Migrations/001_initial_schema.sql | 728 + .../_archived/pre_1.0}/001_initial_schema.sql | 0 .../pre_1.0}/002_lnm_linkset_cache.sql | 0 .../_archived/pre_1.0}/004_documents.sql | 0 .../pre_1.0}/005_connector_state.sql | 0 .../pre_1.0}/006_partition_merge_events.sql | 0 .../006b_migrate_merge_events_data.sql | 0 .../007_generated_columns_advisories.sql | 0 .../_archived/pre_1.0}/008_sync_ledger.sql | 0 .../pre_1.0}/009_advisory_canonical.sql | 0 .../pre_1.0}/010_advisory_source_edge.sql | 0 .../pre_1.0}/011_canonical_functions.sql | 0 .../012_populate_advisory_canonical.sql | 0 .../013_populate_advisory_source_edge.sql | 0 .../014_verify_canonical_migration.sql | 0 .../_archived/pre_1.0}/015_interest_score.sql | 0 .../_archived/pre_1.0}/016_sbom_registry.sql | 0 .../pre_1.0}/017_provenance_scope.sql | 0 .../Advisories/IPostgresAdvisoryStore.cs | 2 +- .../Advisories/PostgresAdvisoryStore.cs | 8 +- .../Postgres}/ConcelierDataSource.cs | 2 +- .../Postgres}/ContractsMappingExtensions.cs | 2 +- .../Conversion/AdvisoryConversionResult.cs | 4 +- .../Postgres}/Conversion/AdvisoryConverter.cs | 4 +- .../Postgres}/DocumentStore.cs | 6 +- .../Models/AdvisoryAffectedEntity.cs | 2 +- .../Postgres}/Models/AdvisoryAliasEntity.cs | 2 +- .../Models/AdvisoryCanonicalEntity.cs | 2 +- .../Postgres}/Models/AdvisoryCreditEntity.cs | 2 +- .../Postgres}/Models/AdvisoryCvssEntity.cs | 2 +- .../Postgres}/Models/AdvisoryEntity.cs | 2 +- .../Models/AdvisoryLinksetCacheEntity.cs | 2 +- .../Models/AdvisoryReferenceEntity.cs | 2 +- .../Models/AdvisorySnapshotEntity.cs | 2 +- .../Models/AdvisorySourceEdgeEntity.cs | 2 +- .../Models/AdvisoryWeaknessEntity.cs | 2 +- .../Postgres}/Models/DocumentRecordEntity.cs | 2 +- .../Postgres}/Models/FeedSnapshotEntity.cs | 2 +- .../Postgres}/Models/KevFlagEntity.cs | 2 +- .../Postgres}/Models/MergeEventEntity.cs | 2 +- .../Postgres}/Models/ProvenanceScopeEntity.cs | 2 +- .../Postgres}/Models/SitePolicyEntity.cs | 2 +- .../Postgres}/Models/SourceEntity.cs | 2 +- .../Postgres}/Models/SourceStateEntity.cs | 2 +- .../Postgres}/Models/SyncLedgerEntity.cs | 2 +- .../AdvisoryAffectedRepository.cs | 4 +- .../Repositories/AdvisoryAliasRepository.cs | 4 +- .../AdvisoryCanonicalRepository.cs | 4 +- .../Repositories/AdvisoryCreditRepository.cs | 4 +- .../Repositories/AdvisoryCvssRepository.cs | 4 +- .../AdvisoryLinksetCacheRepository.cs | 4 +- .../AdvisoryReferenceRepository.cs | 4 +- .../Repositories/AdvisoryRepository.cs | 4 +- .../AdvisorySnapshotRepository.cs | 4 +- .../AdvisoryWeaknessRepository.cs | 4 +- .../Repositories/DocumentRepository.cs | 4 +- .../Repositories/FeedSnapshotRepository.cs | 4 +- .../IAdvisoryAffectedRepository.cs | 4 +- .../Repositories/IAdvisoryAliasRepository.cs | 4 +- .../IAdvisoryCanonicalRepository.cs | 4 +- .../Repositories/IAdvisoryCreditRepository.cs | 4 +- .../Repositories/IAdvisoryCvssRepository.cs | 4 +- .../IAdvisoryReferenceRepository.cs | 4 +- .../Repositories/IAdvisoryRepository.cs | 4 +- .../IAdvisorySnapshotRepository.cs | 4 +- .../IAdvisoryWeaknessRepository.cs | 4 +- .../Repositories/IFeedSnapshotRepository.cs | 4 +- .../Repositories/IKevFlagRepository.cs | 4 +- .../Repositories/IMergeEventRepository.cs | 4 +- .../IProvenanceScopeRepository.cs | 4 +- .../Repositories/ISourceRepository.cs | 4 +- .../Repositories/ISourceStateRepository.cs | 4 +- .../Repositories/ISyncLedgerRepository.cs | 4 +- .../Repositories/InterestScoreRepository.cs | 2 +- .../Repositories/KevFlagRepository.cs | 4 +- .../Repositories/MergeEventRepository.cs | 4 +- .../PostgresChangeHistoryStore.cs | 2 +- .../Repositories/PostgresDtoStore.cs | 4 +- .../Repositories/PostgresExportStateStore.cs | 2 +- .../Repositories/PostgresJpFlagStore.cs | 2 +- .../PostgresProvenanceScopeStore.cs | 4 +- .../Repositories/PostgresPsirtFlagStore.cs | 2 +- .../Repositories/ProvenanceScopeRepository.cs | 4 +- .../Repositories/SbomRegistryRepository.cs | 2 +- .../Repositories/SourceRepository.cs | 4 +- .../Repositories/SourceStateRepository.cs | 4 +- .../Repositories/SyncLedgerRepository.cs | 4 +- .../Postgres}/ServiceCollectionExtensions.cs | 10 +- .../Postgres}/SourceStateAdapter.cs | 6 +- .../Sync/SitePolicyEnforcementService.cs | 6 +- .../StellaOps.Concelier.Persistence.csproj} | 26 +- ...Ops.Concelier.ProofService.Postgres.csproj | 6 +- .../StellaOps.Concelier.ProofService.csproj | 2 +- .../StellaOps.Concelier.RawModels.csproj | 2 +- ...StellaOps.Concelier.SbomIntegration.csproj | 16 +- .../StellaOps.Concelier.SourceIntel.csproj | 2 +- .../AGENTS.md | 30 - .../Converters/Importers/ParityRunner.cs | 38 - .../CachePerformanceBenchmarkTests.cs | 1 - ...llaOps.Concelier.Cache.Valkey.Tests.csproj | 7 +- .../CccsConnectorTests.cs | 4 +- .../Internal/CccsHtmlParserTests.cs | 1 - ...aOps.Concelier.Connector.Cccs.Tests.csproj | 2 +- .../CertBundConnectorTests.cs | 4 +- ....Concelier.Connector.CertBund.Tests.csproj | 2 +- .../CertCc/CertCcConnectorFetchTests.cs | 2 +- .../CertCc/CertCcConnectorTests.cs | 2 +- ...ps.Concelier.Connector.CertCc.Tests.csproj | 3 +- .../CertIn/CertInConnectorTests.cs | 2 +- ...ps.Concelier.Connector.CertIn.Tests.csproj | 1 + .../Common/SourceStateSeedProcessorTests.cs | 2 +- ...ps.Concelier.Connector.Common.Tests.csproj | 20 +- .../Cve/CveConnectorTests.cs | 1 - .../Cve/CveParserSnapshotTests.cs | 6 +- ...laOps.Concelier.Connector.Cve.Tests.csproj | 2 +- .../AlpineConnectorTests.cs | 4 +- .../AlpineDependencyInjectionRoutineTests.cs | 3 +- ...elier.Connector.Distro.Alpine.Tests.csproj | 2 +- .../DebianConnectorTests.cs | 5 +- ...elier.Connector.Distro.Debian.Tests.csproj | 1 + .../RedHat/RedHatConnectorTests.cs | 3 +- ...elier.Connector.Distro.RedHat.Tests.csproj | 1 + .../SuseConnectorTests.cs | 5 +- .../UbuntuConnectorTests.cs | 4 +- .../EpssConnectorTests.cs | 12 +- ...aOps.Concelier.Connector.Epss.Tests.csproj | 4 +- ...aOps.Concelier.Connector.Ghsa.Tests.csproj | 2 +- .../IcsCisaConnectorTests.cs | 4 +- .../Kaspersky/KasperskyConnectorTests.cs | 2 +- ...elier.Connector.Ics.Kaspersky.Tests.csproj | 1 + .../Jvn/JvnConnectorTests.cs | 3 +- ...laOps.Concelier.Connector.Jvn.Tests.csproj | 1 + .../Kev/KevConnectorTests.cs | 2 +- ...laOps.Concelier.Connector.Kev.Tests.csproj | 3 +- .../KisaConnectorTests.cs | 5 +- ...aOps.Concelier.Connector.Kisa.Tests.csproj | 6 +- .../Nvd/NvdConnectorTests.cs | 2 +- .../Nvd/NvdParserSnapshotTests.cs | 2 +- ...laOps.Concelier.Connector.Nvd.Tests.csproj | 3 +- .../Osv/OsvSnapshotTests.cs | 1 - .../RuBduConnectorSnapshotTests.cs | 2 - ...ps.Concelier.Connector.Ru.Bdu.Tests.csproj | 2 +- .../RuNkckiConnectorTests.cs | 4 +- .../RuNkckiJsonParserTests.cs | 5 +- ....Concelier.Connector.Ru.Nkcki.Tests.csproj | 3 +- .../MirrorSignatureVerifierTests.cs | 1 - ...ier.Connector.StellaOpsMirror.Tests.csproj | 3 +- .../StellaOpsMirrorConnectorTests.cs | 3 +- .../Adobe/AdobeConnectorFetchTests.cs | 3 +- ...oncelier.Connector.Vndr.Adobe.Tests.csproj | 1 + .../Apple/AppleConnectorTests.cs | 2 +- ...oncelier.Connector.Vndr.Apple.Tests.csproj | 1 + .../Chromium/ChromiumConnectorTests.cs | 2 +- ...elier.Connector.Vndr.Chromium.Tests.csproj | 1 + ...oncelier.Connector.Vndr.Cisco.Tests.csproj | 2 +- .../MsrcConnectorTests.cs | 4 +- ...Concelier.Connector.Vndr.Msrc.Tests.csproj | 3 +- .../Oracle/OracleConnectorTests.cs | 3 +- ...ncelier.Connector.Vndr.Oracle.Tests.csproj | 1 + ...ncelier.Connector.Vndr.Vmware.Tests.csproj | 1 + .../Vmware/VmwareConnectorTests.cs | 17 +- .../Canonical/CanonicalDeduplicationTests.cs | 6 + .../JobCoordinatorTests.cs | 3 +- .../JobPluginRegistrationExtensionsTests.cs | 3 +- .../JobSchedulerBuilderTests.cs | 3 +- .../Schemas/SchemaManifestTests.cs | 2 +- .../StellaOps.Concelier.Core.Tests.csproj | 12 +- ...ExporterDependencyInjectionRoutineTests.cs | 3 +- .../JsonFeedExporterTests.cs | 3 +- .../TrivyDbFeedExporterTests.cs | 3 +- ...tellaOps.Concelier.Federation.Tests.csproj | 12 +- ...ellaOps.Concelier.Integration.Tests.csproj | 4 +- .../StellaOps.Concelier.Interest.Tests.csproj | 17 +- .../MergeUsageAnalyzerTests.cs | 12 +- ...Ops.Concelier.Merge.Analyzers.Tests.csproj | 15 +- .../AdvisoryMergeServiceTests.cs | 2 + .../AdvisoryPrecedenceMergerTests.cs | 3 +- .../MergeExportSnapshotTests.cs | 6 +- .../StellaOps.Concelier.Merge.Tests.csproj | 6 +- .../CanonicalJsonSerializerTests.cs | 3 +- .../Fixtures/ghsa-semver.actual.json | 128 + .../Fixtures/kev-flag.actual.json | 46 + .../Fixtures/nvd-basic.actual.json | 123 + .../Fixtures/psirt-overlay.actual.json | 126 + .../OsvGhsaParityDiagnosticsTests.cs | 3 +- .../ProvenanceDiagnosticsTests.cs | 3 +- .../AdvisoryCanonicalRepositoryTests.cs | 7 +- .../AdvisoryIdempotencyTests.cs | 7 +- .../AdvisoryRepositoryTests.cs | 7 +- .../ConcelierMigrationTests.cs | 3 +- .../ConcelierPostgresFixture.cs | 4 +- .../ConcelierQueryDeterminismTests.cs | 7 +- .../InterestScoreRepositoryTests.cs | 5 +- .../InterestScoringServiceIntegrationTests.cs | 5 +- .../KevFlagRepositoryTests.cs | 7 +- .../AdvisoryLinksetCacheRepositoryTests.cs | 6 +- .../MergeEventRepositoryTests.cs | 7 +- .../Performance/AdvisoryPerformanceTests.cs | 10 +- .../ProvenanceScopeRepositoryTests.cs | 7 +- .../RepositoryIntegrationTests.cs | 11 +- .../SourceRepositoryTests.cs | 7 +- .../SourceStateRepositoryTests.cs | 7 +- ...llaOps.Concelier.Persistence.Tests.csproj} | 17 +- .../SyncLedgerRepositoryTests.cs | 9 +- ...ncelier.ProofService.Postgres.Tests.csproj | 23 +- ...StellaOps.Concelier.RawModels.Tests.csproj | 17 +- .../SbomParserTests.cs | 3 +- ...Ops.Concelier.SbomIntegration.Tests.csproj | 17 +- .../PatchHeaderParserTests.cs | 2 +- ...ellaOps.Concelier.SourceIntel.Tests.csproj | 11 +- .../ConcelierHealthEndpointTests.cs | 34 +- .../ConcelierTimelineCursorTests.cs | 3 +- .../ConcelierTimelineEndpointTests.cs | 3 +- .../Fixtures/ConcelierApplicationFactory.cs | 34 +- .../InterestScoreEndpointTests.cs | 6 +- .../OrchestratorEndpointsTests.cs | 15 +- .../PluginLoaderTests.cs | 6 +- .../Security/ConcelierAuthorizationTests.cs | 28 +- ...tellaOps.Concelier.WebService.Tests.csproj | 6 +- .../VulnExplorerTelemetryTests.cs | 3 +- .../WebServiceEndpointsTests.cs | 11 +- ...ellaOps.Cryptography.Profiles.Ecdsa.csproj | 2 +- ...ellaOps.Cryptography.Profiles.EdDsa.csproj | 4 +- .../StellaOps.Cryptography.sln | 229 +- .../StellaOps.Cryptography.csproj | 4 +- src/Directory.Build.props | 217 +- src/Directory.Build.targets | 16 +- src/Directory.Packages.props | 169 + src/Directory.Versions.props | 151 + .../StellaOps.EvidenceLocker.sln | 801 +- .../Api/VerdictEndpoints.cs | 25 +- .../Properties/launchSettings.json | 12 + .../Services/EvidencePortableBundleService.cs | 13 +- ...laOps.EvidenceLocker.Infrastructure.csproj | 18 +- .../DatabaseMigrationTests.cs | 4 +- .../EvidenceBundleBuilderTests.cs | 3 +- .../EvidenceBundleImmutabilityTests.cs | 25 +- .../EvidenceBundlePackagingServiceTests.cs | 8 +- .../EvidenceLockerIntegrationTests.cs | 53 +- .../EvidenceLockerWebApplicationFactory.cs | 4 +- .../EvidenceLockerWebServiceContractTests.cs | 65 +- .../EvidenceLockerWebServiceTests.cs | 51 +- .../EvidencePortableBundleServiceTests.cs | 8 +- .../EvidenceSignatureServiceTests.cs | 3 +- .../EvidenceSnapshotServiceTests.cs | 3 +- .../FileSystemEvidenceObjectStoreTests.cs | 7 +- .../GoldenFixturesTests.cs | 3 +- .../S3EvidenceObjectStoreTests.cs | 3 +- .../StellaOps.EvidenceLocker.Tests.csproj | 15 +- ...neIndexerEvidenceTimelinePublisherTests.cs | 3 +- .../Program.cs | 6 + ...StellaOps.EvidenceLocker.WebService.csproj | 8 +- .../StellaOps.EvidenceLocker.Worker.csproj | 2 +- .../StellaOps.EvidenceLocker.csproj | 31 +- .../StellaOps.EvidenceLocker.sln | 90 - .../Endpoints/MirrorRegistrationEndpoints.cs | 6 +- .../Endpoints/ResolveEndpoint.cs | 2 +- .../StellaOps.Excititor.WebService/Program.cs | 32 +- .../Properties/launchSettings.json | 12 + .../Services/PostgresGraphOverlayStore.cs | 2 +- .../Services/VexSignatureVerifierV1Adapter.cs | 141 + .../StellaOps.Excititor.WebService.csproj | 18 +- .../VexWorkerOrchestratorClient.cs | 20 +- .../StellaOps.Excititor.Worker/Program.cs | 34 +- .../Scheduling/DefaultVexProviderRunner.cs | 3 +- .../Scheduling/VexConsensusRefreshService.cs | 3 + .../Signature/VerifyingVexRawDocumentSink.cs | 1 + .../Signature/WorkerSignatureVerifier.cs | 1 + .../StellaOps.Excititor.Worker.csproj | 6 +- src/Excititor/StellaOps.Excititor.sln | 1435 +- ...ellaOps.Excititor.ArtifactStores.S3.csproj | 6 +- .../Dsse/DsseEnvelope.cs | 16 +- .../StellaOps.Excititor.Attestation.csproj | 6 +- .../VexAttestationVerificationOptions.cs | 4 +- ...s.Excititor.Connectors.Abstractions.csproj | 7 +- ...Ops.Excititor.Connectors.Cisco.CSAF.csproj | 10 +- ...aOps.Excititor.Connectors.MSRC.CSAF.csproj | 10 +- ...titor.Connectors.OCI.OpenVEX.Attest.csproj | 8 +- .../OracleCsafConnector.cs | 2 +- ...ps.Excititor.Connectors.Oracle.CSAF.csproj | 10 +- ...ps.Excititor.Connectors.RedHat.CSAF.csproj | 10 +- .../RancherHubConnector.cs | 4 +- ...titor.Connectors.SUSE.RancherVEXHub.csproj | 10 +- ...ps.Excititor.Connectors.Ubuntu.CSAF.csproj | 10 +- .../UbuntuCsafConnector.cs | 4 +- .../AutoVex/VexNotReachableJustification.cs | 20 +- .../Dsse/DsseEnvelope.cs | 23 + .../Observations/VexDeltaModels.cs | 290 + .../StellaOps.Excititor.Core.csproj | 10 +- .../Storage/VexConsensusStoreAbstractions.cs | 16 + .../Verification/CryptoProfileSelector.cs | 178 + .../Verification/IVexSignatureVerifierV2.cs | 165 + .../Verification/IssuerDirectoryClient.cs | 390 + .../ProductionVexSignatureVerifier.cs | 815 + .../Verification/VerificationCacheService.cs | 175 + .../VexSignatureVerifierOptions.cs | 162 + .../Verification/VexVerificationMetrics.cs | 92 + .../Verification/VexVerificationModels.cs | 340 + ...VerificationServiceCollectionExtensions.cs | 154 + .../VexCanonicalJsonSerializer.cs | 2 + .../VexConsensusHold.cs | 2 + .../VexExporterAbstractions.cs | 2 + .../StellaOps.Excititor.Export.csproj | 8 +- .../VexExportEnvelopeBuilder.cs | 2 + .../StellaOps.Excititor.Formats.CSAF.csproj | 4 +- .../CycloneDxExporter.cs | 2 +- ...ellaOps.Excititor.Formats.CycloneDX.csproj | 4 +- .../MergeTraceWriter.cs | 2 +- ...StellaOps.Excititor.Formats.OpenVEX.csproj | 4 +- .../EfCore/Context/ExcititorDbContext.cs | 21 + .../ExcititorPersistenceExtensions.cs} | 17 +- .../Migrations/001_initial_schema.sql | 419 + .../_archived/pre_1.0}/001_initial_schema.sql | 0 .../_archived/pre_1.0}/002_vex_raw_store.sql | 0 .../_archived/pre_1.0}/003_enable_rls.sql | 0 .../pre_1.0}/004_generated_columns_vex.sql | 0 .../005_partition_timeline_events.sql | 0 .../005b_migrate_timeline_events_data.sql | 0 .../_archived/pre_1.0}/006_calibration.sql | 0 .../Postgres}/ExcititorDataSource.cs | 2 +- .../Postgres}/Models/ProjectEntity.cs | 2 +- .../Postgres}/Models/VexStatementEntity.cs | 2 +- .../Repositories/IVexStatementRepository.cs | 4 +- .../PostgresAppendOnlyCheckpointStore.cs | 2 +- .../PostgresAppendOnlyLinksetStore.cs | 2 +- .../PostgresConnectorStateRepository.cs | 2 +- .../PostgresVexAttestationStore.cs | 2 +- .../PostgresVexDeltaRepository.cs | 407 + .../PostgresVexObservationStore.cs | 2 +- .../Repositories/PostgresVexProviderStore.cs | 2 +- .../Repositories/PostgresVexRawStore.cs | 2 +- .../PostgresVexTimelineEventStore.cs | 2 +- .../Repositories/VexStatementRepository.cs | 4 +- .../Repositories/IVexDeltaRepository.cs | 177 + .../StellaOps.Excititor.Persistence.csproj | 34 + .../IVexPolicyProvider.cs | 2 + .../StellaOps.Excititor.Policy.csproj | 6 +- .../VexPolicyBinder.cs | 2 + .../VexPolicyDigest.cs | 2 + .../VexPolicyProcessing.cs | 2 + ...tellaOps.Excititor.Storage.Postgres.csproj | 22 - .../S3ArtifactClientTests.cs | 3 +- ...s.Excititor.ArtifactStores.S3.Tests.csproj | 2 +- ...ellaOps.Excititor.Attestation.Tests.csproj | 15 +- .../VexAttestationClientTests.cs | 1 + .../VexAttestationVerifierTests.cs | 6 +- .../CiscoCsafNormalizerTests.cs | 16 +- .../Connectors/CiscoCsafConnectorTests.cs | 9 + ...cititor.Connectors.Cisco.CSAF.Tests.csproj | 8 +- .../Connectors/MsrcCsafConnectorTests.cs | 31 +- .../MsrcCsafNormalizerTests.cs | 8 +- ...xcititor.Connectors.MSRC.CSAF.Tests.csproj | 8 +- .../OciOpenVexAttestationConnectorTests.cs | 26 +- ...Connectors.OCI.OpenVEX.Attest.Tests.csproj | 9 +- .../Connectors/OracleCsafConnectorTests.cs | 5 + .../OracleCsafNormalizerTests.cs | 8 +- ...ititor.Connectors.Oracle.CSAF.Tests.csproj | 6 +- .../Connectors/RedHatCsafConnectorTests.cs | 5 + .../RedHatCsafNormalizerTests.cs | 20 +- ...ititor.Connectors.RedHat.CSAF.Tests.csproj | 8 +- .../Connectors/RancherHubConnectorTests.cs | 447 - .../RancherVexHubNormalizerTests.cs | 28 +- ...Connectors.SUSE.RancherVEXHub.Tests.csproj | 10 +- .../Connectors/UbuntuCsafConnectorTests.cs | 41 +- ...ititor.Connectors.Ubuntu.CSAF.Tests.csproj | 6 +- .../UbuntuCsafNormalizerTests.cs | 9 +- .../ExcititorAssemblyDependencyTests.cs | 1 - .../AutoVex/AutoVexDowngradeServiceTests.cs | 536 +- .../ExcititorNoLatticeComputationTests.cs | 58 +- .../PreservePrune/PreservePruneSourceTests.cs | 45 +- .../StellaOps.Excititor.Core.Tests.csproj | 9 +- .../ProductionVexSignatureVerifierTests.cs | 634 + .../VexPolicyBinderTests.cs | 3 +- .../VexPolicyDiagnosticsTests.cs | 3 +- .../Observations/VexLinksetTests.cs | 260 + .../StellaOps.Excititor.Core.UnitTests.csproj | 10 +- .../VexEvidenceChunkServiceTests.cs | 10 + .../MirrorBundlePublisherTests.cs | 3 +- .../OfflineBundleArtifactStoreTests.cs | 3 +- .../S3ArtifactStoreTests.cs | 3 +- .../StellaOps.Excititor.Export.Tests.csproj | 2 +- .../CsafExporterTests.cs | 3 +- .../Snapshots/CsafExportSnapshotTests.cs | 1 - ...llaOps.Excititor.Formats.CSAF.Tests.csproj | 5 +- .../CycloneDxExporterTests.cs | 3 +- .../Snapshots/CycloneDxExportSnapshotTests.cs | 3 +- ...s.Excititor.Formats.CycloneDX.Tests.csproj | 5 +- .../OpenVexExporterTests.cs | 13 +- .../Snapshots/OpenVexExportSnapshotTests.cs | 9 +- ...Ops.Excititor.Formats.OpenVEX.Tests.csproj | 6 +- .../ExcititorMigrationTests.cs | 3 +- .../ExcititorPostgresFixture.cs | 6 +- .../PostgresAppendOnlyLinksetStoreTests.cs | 7 +- .../PostgresVexAttestationStoreTests.cs | 6 +- .../PostgresVexObservationStoreTests.cs | 6 +- .../PostgresVexProviderStoreTests.cs | 6 +- .../PostgresVexTimelineEventStoreTests.cs | 6 +- ...ellaOps.Excititor.Persistence.Tests.csproj | 27 + .../VexQueryDeterminismTests.cs | 18 +- .../VexStatementIdempotencyTests.cs | 12 +- .../PluginCatalogTests.cs | 276 + .../StellaOps.Excititor.Plugin.Tests.csproj | 44 + .../VexConnectorRegistrationTests.cs | 249 + .../VexPolicyProviderTests.cs | 26 +- ...ps.Excititor.Storage.Postgres.Tests.csproj | 43 - .../AirgapImportEndpointTests.cs | 3 +- .../AirgapSignerTrustServiceTests.cs | 3 +- .../AttestationVerifyEndpointTests.cs | 3 +- .../Auth/AuthenticationEnforcementTests.cs | 1 - .../Contract/OpenApiContractSnapshotTests.cs | 1 - .../EvidenceLockerEndpointTests.cs | 1 - .../EvidenceTelemetryTests.cs | 3 +- .../GraphOverlayFactoryTests.cs | 4 +- .../IngestEndpointsTests.cs | 3 +- .../MirrorEndpointsTests.cs | 3 +- .../Observability/OTelTraceAssertionTests.cs | 1 - .../ObservabilityEndpointTests.cs | 3 +- .../PolicyEndpointsTests.cs | 3 +- .../ResolveEndpointTests.cs | 3 +- .../RiskFeedEndpointsTests.cs | 3 +- ...tellaOps.Excititor.WebService.Tests.csproj | 18 +- .../TestServiceOverrides.cs | 3 + .../VerificationIntegrationTests.cs | 342 + .../VexAttestationLinkEndpointTests.cs | 3 +- .../VexEvidenceChunksEndpointTests.cs | 3 +- .../VexGuardSchemaTests.cs | 3 +- .../VexLinksetListEndpointTests.cs | 3 +- .../VexObservationListEndpointTests.cs | 3 +- .../VexRawEndpointsTests.cs | 3 +- .../DefaultVexProviderRunnerTests.cs | 2 +- .../EndToEnd/EndToEndIngestJobTests.cs | 204 +- .../WorkerOTelCorrelationTests.cs | 1 - .../VexWorkerOrchestratorClientTests.cs | 3 +- .../Retry/WorkerRetryPolicyTests.cs | 175 +- .../Signature/WorkerSignatureVerifierTests.cs | 2 +- .../StellaOps.Excititor.Worker.Tests.csproj | 22 +- .../TenantAuthorityClientFactoryTests.cs | 3 +- .../StellaOps.ExportCenter.RiskBundles.csproj | 4 +- src/ExportCenter/StellaOps.ExportCenter.sln | 831 +- .../ExportCenterClientTests.cs | 3 +- .../ExportDownloadHelperTests.cs | 3 +- ...StellaOps.ExportCenter.Client.Tests.csproj | 11 +- .../StellaOps.ExportCenter.Client.csproj | 6 +- .../Domain/LineageEvidencePack.cs | 487 + .../Services/EvidencePackSigningService.cs | 411 + .../Services/IEvidencePackSigningService.cs | 166 + .../Services/ILineageEvidencePackService.cs | 103 + .../Services/LineageEvidencePackService.cs | 567 + .../StellaOps.ExportCenter.Core.csproj | 4 +- ...ellaOps.ExportCenter.Infrastructure.csproj | 10 +- .../Adapters/ExportAdapterRegistryTests.cs | 3 +- .../AttestationBundleBuilderTests.cs | 3 +- .../BootstrapPackBuilderTests.cs | 3 +- .../BundleEncryptionServiceTests.cs | 3 +- .../DevPortalOfflineBundleBuilderTests.cs | 11 +- .../DevPortalOfflineJobTests.cs | 7 +- ...HmacDevPortalOfflineManifestSignerTests.cs | 7 +- .../MirrorBundleBuilderTests.cs | 3 +- .../MirrorBundleSigningTests.cs | 3 +- .../MirrorDeltaAdapterTests.cs | 3 +- .../OfflineBundle/BundleVerificationTests.cs | 91 +- .../OfflineBundlePackagerTests.cs | 79 +- .../PortableEvidenceExportBuilderTests.cs | 3 +- .../RiskBundleBuilderTests.cs | 7 +- .../RiskBundleJobTests.cs | 5 +- .../RiskBundleSignerTests.cs | 2 +- .../StellaOps.ExportCenter.Tests.csproj | 48 +- .../Oci/AIAttestationOciDiscovery.cs | 14 +- .../Oci/AIAttestationOciPublisher.cs | 10 - .../Distribution/Oci/OciDistributionModels.cs | 4 + .../Lineage/LineageExportEndpoints.cs | 424 + .../Lineage/LineageExportServiceExtensions.cs | 27 + .../Program.cs | 7 + .../StellaOps.ExportCenter.WebService.csproj | 8 +- .../StellaOps.ExportCenter.Worker/Program.cs | 4 +- .../StellaOps.ExportCenter.Worker.csproj | 20 +- .../StellaOps.ExportCenter.sln | 90 - .../StellaOps.Feedser.Core.csproj | 2 +- src/Feedser/StellaOps.Feedser.sln | 75 + .../StellaOps.Feedser.Core.Tests.csproj | 12 +- .../AirgapAndOrchestratorServiceTests.cs | 25 + .../Directory.Build.props | 12 - .../Exports/ExportFiltersHashTests.cs | 27 +- .../FindingsLedgerIntegrationTests.cs | 144 +- .../FindingsLedgerWebServiceContractTests.cs | 3 +- .../LedgerIncidentCoordinatorTests.cs | 12 +- .../LedgerReplayDeterminismTests.cs | 11 +- .../Observability/LedgerMetricsTests.cs | 12 +- .../Observability/TestLogger.cs | 2 +- .../LedgerEventWriteServiceIncidentTests.cs | 23 +- .../StellaOps.Findings.Ledger.Tests.csproj | 26 +- .../Program.cs | 15 +- .../Properties/launchSettings.json | 12 + .../Services/FindingScoringService.cs | 2 +- ...tellaOps.Findings.Ledger.WebService.csproj | 6 +- .../AttachmentEncryptionService.cs | 2 +- .../StellaOps.Findings.Ledger.csproj | 9 +- .../LedgerReplayHarness.csproj | 2 +- .../tools/LedgerReplayHarness/Program.cs | 150 +- src/Findings/StellaOps.Findings.sln | 705 + .../HarnessRunnerTests.cs | 3 +- .../LedgerMetricsTests.cs | 3 +- .../PolicyEngineEvaluationServiceTests.cs | 3 +- .../Schema/OpenApiSchemaTests.cs | 24 +- .../StellaOps.Findings.Ledger.Tests.csproj | 13 +- .../LedgerReplayHarness/HarnessRunner.cs | 6 +- .../LedgerReplayHarness.csproj | 2 +- .../tools/LedgerReplayHarness/Program.cs | 53 +- .../Properties/launchSettings.json | 12 + .../StellaOps.Gateway.WebService.csproj | 14 +- src/Gateway/StellaOps.Gateway.sln | 378 + .../AuthorizationMiddlewareTests.cs | 2 +- .../EffectiveClaimsStoreTests.cs | 1 + .../GatewayHealthTests.cs | Bin 0 -> 1472 bytes .../StellaOps.Gateway.WebService.Tests.csproj | 18 +- .../Contracts/ReachabilityContracts.cs | 348 + src/Graph/StellaOps.Graph.Api/Program.cs | 14 +- .../Properties/launchSettings.json | 12 + .../Services/IReachabilityDeltaService.cs | 57 + .../Services/InMemoryGraphQueryService.cs | 2 +- .../Services/InMemoryGraphSearchService.cs | 2 +- .../Services/InMemoryOverlayService.cs | 2 +- .../InMemoryReachabilityDeltaService.cs | 334 + .../GraphIndexerPostgresFixture.cs | 26 - ...raph.Indexer.Storage.Postgres.Tests.csproj | 35 - ...aOps.Graph.Indexer.Storage.Postgres.csproj | 12 - .../Documents/GraphSnapshotBuilder.cs | 2 +- .../StellaOps.Graph.Indexer.csproj | 10 +- src/Graph/StellaOps.Graph.sln | 143 + .../EfCore/Context/GraphIndexerDbContext.cs | 21 + .../GraphIndexerPersistenceExtensions.cs} | 24 +- .../Postgres}/GraphIndexerDataSource.cs | 2 +- .../PostgresGraphAnalyticsWriter.cs | 2 +- .../PostgresGraphDocumentWriter.cs | 2 +- .../PostgresGraphSnapshotProvider.cs | 2 +- .../Repositories/PostgresIdempotencyStore.cs | 2 +- ...StellaOps.Graph.Indexer.Persistence.csproj | 26 + .../AuditLoggerTests.cs | 2 +- .../GraphApiContractTests.cs | 5 +- .../StellaOps.Graph.Api.Tests/MetricsTests.cs | 3 +- .../QueryServiceTests.cs | 3 +- .../SearchServiceTests.cs | 5 +- .../StellaOps.Graph.Api.Tests.csproj | 3 +- .../GraphIndexerPostgresFixture.cs | 116 + .../GraphQueryDeterminismTests.cs | 7 +- .../GraphStorageMigrationTests.cs | 5 +- .../PostgresIdempotencyStoreTests.cs | 7 +- ...Ops.Graph.Indexer.Persistence.Tests.csproj | 25 + .../GraphAnalyticsPipelineTests.cs | 3 +- .../GraphChangeStreamProcessorTests.cs | 5 +- .../GraphCoreLogicTests.cs | 65 +- .../GraphIndexerEndToEndTests.cs | 53 +- .../GraphTestHelpers.cs | 147 + .../SbomSnapshotExporterTests.cs | 2 +- .../StellaOps.Graph.Indexer.Tests.csproj | 8 +- .../StellaOps.IssuerDirectory.sln | 379 + .../IssuerDirectoryClientTests.cs | 3 +- ...tellaOps.IssuerDirectory.Core.Tests.csproj | 4 +- .../StellaOps.IssuerDirectory.Core.csproj | 2 +- ...aOps.IssuerDirectory.Infrastructure.csproj | 8 +- ...ps.IssuerDirectory.Storage.Postgres.csproj | 31 - .../Program.cs | 5 +- .../Properties/launchSettings.json | 12 + ...tellaOps.IssuerDirectory.WebService.csproj | 18 +- .../StellaOps.IssuerDirectory.sln | 49 - .../IssuerDirectoryPostgresFixture.cs | 28 - .../IssuerKeyRepositoryTests.cs | 301 - .../IssuerRepositoryTests.cs | 231 - .../IssuerTrustRepositoryTests.cs | 200 - ...uerDirectory.Storage.Postgres.Tests.csproj | 38 - .../Context/IssuerDirectoryDbContext.cs | 21 + .../IssuerDirectoryPersistenceExtensions.cs} | 13 +- .../Migrations/001_initial_schema.sql | 0 .../Postgres}/IssuerDirectoryDataSource.cs | 2 +- .../Repositories/PostgresIssuerAuditSink.cs | 2 +- .../PostgresIssuerKeyRepository.cs | 2 +- .../Repositories/PostgresIssuerRepository.cs | 2 +- .../PostgresIssuerTrustRepository.cs | 2 +- ...ellaOps.IssuerDirectory.Persistence.csproj | 35 + ...uerDirectory.Persistence.csproj.Backup.tmp | 35 + .../IssuerAuditSinkTests.cs | 5 +- .../IssuerDirectoryPostgresCollection.cs | 9 + .../IssuerDirectoryPostgresFixture.cs | 6 +- .../IssuerKeyRepositoryTests.cs | 70 + .../IssuerRepositoryTests.cs | 32 +- ...s.IssuerDirectory.Persistence.Tests.csproj | 21 + .../TrustRepositoryTests.cs | 48 +- .../IssuerKeyRepositoryTests.cs | 77 - ...uerDirectory.Storage.Postgres.Tests.csproj | 29 - src/Notifier/StellaOps.Notifier.sln | 322 +- .../AttestationEventEndpointTests.cs | 2 +- .../AttestationTemplateSeederTests.cs | 8 +- .../Correlation/CorrelationEngineTests.cs | 22 +- .../Correlation/IncidentManagerTests.cs | 124 +- .../Correlation/NotifyThrottlerTests.cs | 68 +- .../OperatorOverrideServiceTests.cs | 86 +- .../QuietHourCalendarServiceTests.cs | 72 +- .../Correlation/QuietHoursEvaluatorTests.cs | 62 +- .../SuppressionAuditLoggerTests.cs | 70 +- .../Correlation/ThrottleConfigServiceTests.cs | 70 +- .../Digest/DigestGeneratorTests.cs | 77 +- .../Digest/DigestSchedulerTests.cs | 54 +- .../Dispatch/SimpleTemplateRendererTests.cs | 20 +- .../Dispatch/WebhookChannelDispatcherTests.cs | 16 +- .../Endpoints/NotifyApiEndpointsTests.cs | 44 +- .../EventProcessorTests.cs | 10 +- .../Fallback/FallbackHandlerTests.cs | 120 +- .../Localization/LocalizationServiceTests.cs | 52 +- .../Observability/ChaosTestRunnerTests.cs | 90 +- .../Observability/DeadLetterHandlerTests.cs | 24 +- .../RetentionPolicyServiceTests.cs | 10 +- .../OpenApiEndpointTests.cs | 12 +- .../PackApprovalTemplateSeederTests.cs | 10 +- .../PackApprovalTemplateTests.cs | 3 +- .../RiskEventEndpointTests.cs | 2 +- .../RiskTemplateSeederTests.cs | 8 +- .../Security/SigningServiceTests.cs | 60 +- .../Security/TenantIsolationValidatorTests.cs | 136 +- .../Security/WebhookSecurityServiceTests.cs | 49 +- .../Simulation/SimulationEngineTests.cs | 31 +- .../StellaOps.Notifier.Tests.csproj | 21 +- .../EnhancedTemplateRendererTests.cs | 30 +- .../Templates/NotifyTemplateServiceTests.cs | 42 +- .../TenantNotificationEnricherTests.cs | 26 +- .../StellaOps.Notifier.WebService/Program.cs | 4 +- .../StellaOps.Notifier.WebService.csproj | 4 +- .../StellaOps.Notifier.Worker/Program.cs | 5 +- .../StellaOps.Notifier.Worker.csproj | 14 +- .../StellaOps.Notifier/StellaOps.Notifier.sln | 62 - .../Program.Partial.cs | 4 +- .../StellaOps.Notify.WebService/Program.cs | 9 +- .../Properties/launchSettings.json | 12 + .../StellaOps.Notify.WebService.csproj | 10 +- .../StellaOps.Notify.Worker.csproj | 14 +- src/Notify/StellaOps.Notify.sln | 1021 +- .../StellaOps.Notify.Connectors.Email.csproj | 2 +- .../StellaOps.Notify.Connectors.Shared.csproj | 1 + .../StellaOps.Notify.Connectors.Slack.csproj | 2 +- .../StellaOps.Notify.Connectors.Teams.csproj | 2 +- ...StellaOps.Notify.Connectors.Webhook.csproj | 2 +- .../StellaOps.Notify.Models/NotifyEnums.cs | 1 + .../EfCore/Context/NotifyDbContext.cs | 32 + .../NotifyPersistenceExtensions.cs} | 48 +- .../InMemory/Documents/NotifyDocuments.cs | 270 + .../Repositories/INotifyRepositories.cs | 149 + .../Repositories/InMemoryRepositories.cs | 516 + .../Migrations/001_initial_schema.sql | 578 + .../Postgres}/Models/ChannelEntity.cs | 2 +- .../Postgres}/Models/DeliveryEntity.cs | 2 +- .../Postgres}/Models/DigestEntity.cs | 2 +- .../Postgres}/Models/EscalationEntity.cs | 2 +- .../Postgres}/Models/InboxEntity.cs | 2 +- .../Postgres}/Models/IncidentEntity.cs | 2 +- .../Models/LocalizationBundleEntity.cs | 2 +- .../Postgres}/Models/LockEntity.cs | 2 +- .../Models/MaintenanceWindowEntity.cs | 2 +- .../Postgres}/Models/NotifyAuditEntity.cs | 2 +- .../Postgres}/Models/OnCallScheduleEntity.cs | 2 +- .../Models/OperatorOverrideEntity.cs | 2 +- .../Postgres}/Models/QuietHoursEntity.cs | 2 +- .../Postgres}/Models/RuleEntity.cs | 2 +- .../Postgres}/Models/TemplateEntity.cs | 2 +- .../Postgres}/Models/ThrottleConfigEntity.cs | 2 +- .../Postgres}/NotifyDataSource.cs | 2 +- .../Repositories/ChannelRepository.cs | 4 +- .../Repositories/DeliveryRepository.cs | 4 +- .../Repositories/DigestRepository.cs | 4 +- .../Repositories/EscalationRepository.cs | 4 +- .../Repositories/IChannelRepository.cs | 4 +- .../Repositories/IDeliveryRepository.cs | 4 +- .../Repositories/IDigestRepository.cs | 4 +- .../Repositories/IEscalationRepository.cs | 4 +- .../Repositories/IInboxRepository.cs | 4 +- .../Repositories/IIncidentRepository.cs | 4 +- .../ILocalizationBundleRepository.cs | 4 +- .../Postgres}/Repositories/ILockRepository.cs | 4 +- .../IMaintenanceWindowRepository.cs | 4 +- .../Repositories/INotifyAuditRepository.cs | 4 +- .../Repositories/IOnCallScheduleRepository.cs | 4 +- .../IOperatorOverrideRepository.cs | 4 +- .../Repositories/IQuietHoursRepository.cs | 4 +- .../Postgres}/Repositories/IRuleRepository.cs | 4 +- .../Repositories/ITemplateRepository.cs | 4 +- .../Repositories/IThrottleConfigRepository.cs | 4 +- .../Postgres}/Repositories/InboxRepository.cs | 4 +- .../Repositories/IncidentRepository.cs | 4 +- .../LocalizationBundleRepository.cs | 4 +- .../Postgres}/Repositories/LockRepository.cs | 4 +- .../MaintenanceWindowRepository.cs | 4 +- .../Repositories/NotifyAuditRepository.cs | 4 +- .../Repositories/OnCallScheduleRepository.cs | 4 +- .../OperatorOverrideRepository.cs | 4 +- .../Repositories/QuietHoursRepository.cs | 4 +- .../Postgres}/Repositories/RuleRepository.cs | 4 +- .../Repositories/TemplateRepository.cs | 4 +- .../Repositories/ThrottleConfigRepository.cs | 4 +- .../StellaOps.Notify.Persistence.csproj | 36 + .../StellaOps.Notify.Queue.csproj | 22 +- .../ServiceCollectionExtensions.cs | 22 +- .../StellaOps.Notify.Storage.InMemory.csproj | 10 +- .../AGENTS.md | 19 - .../Migrations/001_initial_schema.sql | 340 - .../Migrations/010_enable_rls.sql | 178 - .../Migrations/011_partition_deliveries.sql | 181 - .../011b_migrate_deliveries_data.sql | 165 - .../StellaOps.Notify.Storage.Postgres.csproj | 21 - .../ErrorHandling/EmailConnectorErrorTests.cs | 4 +- ...laOps.Notify.Connectors.Email.Tests.csproj | 14 +- .../ErrorHandling/SlackConnectorErrorTests.cs | 8 +- .../SlackChannelTestProviderTests.cs | 3 +- ...laOps.Notify.Connectors.Slack.Tests.csproj | 14 +- .../ErrorHandling/TeamsConnectorErrorTests.cs | 8 +- ...laOps.Notify.Connectors.Teams.Tests.csproj | 14 +- .../TeamsChannelTestProviderTests.cs | 3 +- .../WebhookConnectorErrorHandlingTests.cs | 15 +- .../WebhookConnectorErrorTests.cs | 277 +- ...Ops.Notify.Connectors.Webhook.Tests.csproj | 18 +- .../NotificationRateLimitingTests.cs | 2 +- .../StellaOps.Notify.Core.Tests.csproj | 12 +- .../Templating/NotificationTemplatingTests.cs | 8 +- .../RateLimiting/NotifyRateLimitingTests.cs | 2 +- .../StellaOps.Notify.Engine.Tests.csproj | 18 +- .../PlatformEventSchemaValidationTests.cs | 2 +- .../StellaOps.Notify.Models.Tests.csproj | 11 +- .../ChannelRepositoryTests.cs | 57 +- .../DeliveryIdempotencyTests.cs | 10 +- .../DeliveryRepositoryTests.cs | 10 +- .../DigestAggregationTests.cs | 121 +- .../DigestRepositoryTests.cs | 10 +- .../EscalationHandlingTests.cs | 107 +- .../InboxRepositoryTests.cs | 73 +- .../NotificationDeliveryFlowTests.cs | 89 +- .../NotifyAuditRepositoryTests.cs | 40 +- .../NotifyMigrationTests.cs | 46 +- .../NotifyPostgresFixture.cs | 12 +- .../RetryStatePersistenceTests.cs | 10 +- .../RuleRepositoryTests.cs | 49 +- .../StellaOps.Notify.Persistence.Tests.csproj | 33 + .../TemplateRepositoryTests.cs | 53 +- .../NatsNotifyDeliveryQueueTests.cs | 46 +- .../NatsNotifyEventQueueTests.cs | 42 +- .../RedisNotifyDeliveryQueueTests.cs | 53 +- .../RedisNotifyEventQueueTests.cs | 53 +- .../StellaOps.Notify.Queue.Tests.csproj | 26 +- ...laOps.Notify.Storage.Postgres.Tests.csproj | 36 - .../CrudEndpointsTests.cs | 24 +- .../NormalizeEndpointsTests.cs | 18 +- .../StellaOps.Notify.WebService.Tests.csproj | 19 +- .../W1/NotifyWebServiceAuthTests.cs | 60 +- .../W1/NotifyWebServiceContractTests.cs | 67 +- .../W1/NotifyWebServiceOTelTests.cs | 45 +- .../StellaOps.Notify.Worker.Tests.csproj | 21 +- .../WK1/NotifyWorkerEndToEndTests.cs | 2 +- .../WK1/NotifyWorkerRetryTests.cs | 4 +- src/Orchestrator/StellaOps.Orchestrator.sln | 350 +- .../StellaOps.Orchestrator.Core.csproj | 2 +- ...ellaOps.Orchestrator.Infrastructure.csproj | 16 +- ...hestrator.Infrastructure.csproj.Backup.tmp | 32 + .../Mirror/MirrorOperationRecorderTests.cs | 6 +- .../Events/TimelineEventTests.cs | 12 +- .../Evidence/JobAttestationTests.cs | 6 +- .../Evidence/JobCapsuleTests.cs | 20 +- .../SchemaSmokeTests.cs | 3 +- .../StellaOps.Orchestrator.Tests.csproj | 43 +- .../Ttfs/FirstSignalServiceTests.cs | 18 +- .../Program.cs | 3 +- .../StellaOps.Orchestrator.WebService.csproj | 12 +- .../StellaOps.Orchestrator.Worker.csproj | 2 +- .../StellaOps.Orchestrator.sln | 90 - src/PacksRegistry/StellaOps.PacksRegistry.sln | 308 +- .../StellaOps.PacksRegistry.sln.bak | 99 + .../StellaOps.PacksRegistry.slnx | 52 + ...llaOps.PacksRegistry.Infrastructure.csproj | 12 +- ...Registry.Infrastructure.csproj.Backup.tmp} | 43 +- .../Context/PacksRegistryDbContext.cs | 42 + .../PacksRegistryPersistenceExtensions.cs | 51 + .../README.md | 55 + ...ps.PacksRegistry.Persistence.EfCore.csproj | 26 + ...acksRegistry.Storage.Postgres.Tests.csproj | 35 - ...aOps.PacksRegistry.Storage.Postgres.csproj | 13 - .../ExportServiceTests.cs | 5 +- .../FilePackRepositoryTests.cs | 2 +- .../PackServiceTests.cs | 6 +- .../PacksApiTests.cs | 5 +- .../RsaSignatureVerifierTests.cs | 7 +- .../StellaOps.PacksRegistry.Tests.csproj | 11 +- .../Program.cs | 1 + .../Properties/launchSettings.json | 12 + .../StellaOps.PacksRegistry.WebService.csproj | 12 +- .../StellaOps.PacksRegistry.Worker.csproj | 2 +- .../StellaOps.PacksRegistry.sln | 90 - .../EfCore/Context/PacksRegistryDbContext.cs | 21 + .../PacksRegistryPersistenceExtensions.cs} | 24 +- .../Postgres}/PacksRegistryDataSource.cs | 2 +- .../PostgresAttestationRepository.cs | 2 +- .../Repositories/PostgresAuditRepository.cs | 2 +- .../PostgresLifecycleRepository.cs | 2 +- .../Repositories/PostgresMirrorRepository.cs | 2 +- .../Repositories/PostgresPackRepository.cs | 2 +- .../Repositories/PostgresParityRepository.cs | 2 +- ...StellaOps.PacksRegistry.Persistence.csproj | 26 + .../PacksRegistryPostgresFixture.cs | 3 +- .../PostgresPackRepositoryTests.cs | 5 +- ...Ops.PacksRegistry.Persistence.Tests.csproj | 25 + .../VexTrustConfidenceFactorProvider.cs | 204 + .../Crypto/CryptoRiskAtoms.cs | 200 + .../Crypto/CryptoRiskEvaluator.cs | 319 + ...VexTrustGateServiceCollectionExtensions.cs | 67 + .../Endpoints/ConflictEndpoints.cs | 4 +- .../Endpoints/RiskProfileSchemaEndpoints.cs | 10 +- .../Endpoints/ViolationEndpoints.cs | 4 +- .../MessagingExceptionEffectiveCache.cs | 4 +- .../RedisExceptionEffectiveCache.cs | 4 +- .../Gates/PolicyGateDecision.cs | 37 + .../Gates/PolicyGateEvaluator.cs | 139 +- .../Gates/PolicyGateOptions.cs | 5 + .../Gates/VexTrustGate.cs | 489 + .../Gates/VexTrustGateMetrics.cs | 125 + .../Gates/VexTrustGateOptions.cs | 165 + src/Policy/StellaOps.Policy.Engine/Program.cs | 8 +- .../Properties/launchSettings.json | 12 + .../IReachabilityFactsSignalsClient.cs | 132 + .../ReachabilityFactsSignalsClient.cs | 83 + .../DualEmitVerdictEnricher.cs | 18 +- .../Services/ExceptionApprovalRulesService.cs | 4 +- .../Services/VerdictLinkService.cs | 165 + .../StellaOps.Policy.Engine.csproj | 23 +- .../InMemory/InMemoryExceptionRepository.cs | 4 +- .../Vex/VexDecisionEmitter.cs | 32 +- .../Vex/VexDecisionModels.cs | 11 + .../Workers/ExceptionLifecycleService.cs | 2 +- .../Endpoints/ExceptionApprovalEndpoints.cs | 4 +- .../StellaOps.Policy.Gateway/Program.cs | 10 +- .../Properties/launchSettings.json | 12 + .../StellaOps.Policy.Gateway.csproj | 10 +- .../StellaOps.Policy.Registry.csproj | 4 +- .../StellaOps.Policy.RiskProfile.csproj | 2 +- .../Engine/CvssV4Engine.cs | 140 +- .../StellaOps.Policy.Scoring.csproj | 2 +- src/Policy/StellaOps.Policy.only.sln | 321 - src/Policy/StellaOps.Policy.sln | 1172 +- .../StellaOps.Policy.Exceptions.csproj | 5 +- .../EfCore/Context/PolicyDbContext.cs | 21 + .../Extensions/PolicyPersistenceExtensions.cs | 86 + .../Migrations/001_initial_schema.sql | 1320 ++ .../_archived/pre_1.0}/002_cvss_receipts.sql | 0 .../pre_1.0}/003_snapshots_violations.sql | 0 .../pre_1.0}/004_epss_risk_scores.sql | 0 .../pre_1.0}/005_cvss_multiversion.sql | 0 .../_archived/pre_1.0}/006_enable_rls.sql | 0 .../pre_1.0}/007_unknowns_registry.sql | 0 .../pre_1.0}/008_exception_objects.sql | 0 .../pre_1.0}/009_exception_applications.sql | 0 .../pre_1.0}/010_recheck_evidence.sql | 0 .../010_unknowns_blast_radius_containment.sql | 0 .../pre_1.0}/011_unknowns_reason_codes.sql | 0 .../_archived/pre_1.0}/012_budget_ledger.sql | 0 .../pre_1.0}/013_exception_approval.sql | 0 .../Migrations/_archived/pre_1.0/README.md | 34 + .../Migration/LegacyDocumentConverter.cs | 2 +- .../Postgres}/Migration/PolicyMigrator.cs | 6 +- .../Postgres}/Models/BudgetLedgerEntity.cs | 2 +- .../Postgres}/Models/ConflictEntity.cs | 2 +- .../Postgres}/Models/EvaluationRunEntity.cs | 2 +- .../Models/ExceptionApprovalEntity.cs | 4 +- .../Postgres}/Models/ExceptionEntity.cs | 2 +- .../Postgres}/Models/ExplanationEntity.cs | 2 +- .../Postgres}/Models/LedgerExportEntity.cs | 2 +- .../Postgres}/Models/PackEntity.cs | 2 +- .../Postgres}/Models/PackVersionEntity.cs | 2 +- .../Postgres/Models/PolicyAuditEntity.cs | 60 + .../Postgres}/Models/RiskProfileEntity.cs | 2 +- .../Postgres}/Models/RuleEntity.cs | 2 +- .../Postgres}/Models/SnapshotEntity.cs | 2 +- .../Postgres}/Models/ViolationEventEntity.cs | 2 +- .../Postgres}/Models/WorkerResultEntity.cs | 2 +- .../Postgres}/PolicyDataSource.cs | 2 +- .../Repositories/ConflictRepository.cs | 4 +- .../Repositories/EvaluationRunRepository.cs | 4 +- .../ExceptionApprovalRepository.cs | 6 +- .../Repositories/ExceptionRepository.cs | 4 +- .../Repositories/ExplanationRepository.cs | 4 +- .../Repositories/IConflictRepository.cs | 4 +- .../Repositories/IEvaluationRunRepository.cs | 4 +- .../IExceptionApprovalRepository.cs | 4 +- .../Repositories/IExceptionRepository.cs | 4 +- .../Repositories/IExplanationRepository.cs | 4 +- .../Repositories/ILedgerExportRepository.cs | 4 +- .../Postgres}/Repositories/IPackRepository.cs | 4 +- .../Repositories/IPackVersionRepository.cs | 4 +- .../Repositories/IPolicyAuditRepository.cs | 4 +- .../Repositories/IRiskProfileRepository.cs | 4 +- .../Postgres}/Repositories/IRuleRepository.cs | 4 +- .../Repositories/ISnapshotRepository.cs | 4 +- .../Repositories/IViolationEventRepository.cs | 4 +- .../Repositories/IWorkerResultRepository.cs | 4 +- .../Repositories/LedgerExportRepository.cs | 4 +- .../Postgres}/Repositories/PackRepository.cs | 4 +- .../Repositories/PackVersionRepository.cs | 4 +- .../Repositories/PolicyAuditRepository.cs | 4 +- .../Repositories/PostgresBudgetStore.cs | 22 +- .../PostgresExceptionObjectRepository.cs | 2 +- .../Repositories/PostgresReceiptRepository.cs | 2 +- .../Repositories/RiskProfileRepository.cs | 4 +- .../Postgres}/Repositories/RuleRepository.cs | 4 +- .../Repositories/SnapshotRepository.cs | 4 +- .../Repositories/ViolationEventRepository.cs | 4 +- .../Repositories/WorkerResultRepository.cs | 4 +- .../Postgres}/ServiceCollectionExtensions.cs | 8 +- .../StellaOps.Policy.Persistence.csproj} | 14 +- .../AGENTS.md | 34 - .../Migrations/001_initial_schema.sql | 220 - .../Models/PolicyAuditEntity.cs | 18 - .../StellaOps.Policy.Unknowns.csproj | 6 +- .../StellaOps.Policy/Crypto/CryptoAsset.cs | 54 + .../StellaOps.Policy/Crypto/CryptoAtoms.cs | 232 + .../Crypto/CryptoRiskRules.cs | 393 + .../Gates/PolicyGateAbstractions.cs | 22 + .../Gates/ReachabilityRequirementGate.cs | 96 + .../PolicyScoringConfigBinder.cs | 2 +- .../RiskProfileDiagnostics.cs | 2 +- .../Snapshots/SnapshotService.cs | 11 + .../StellaOps.Policy/StellaOps.Policy.csproj | 10 +- .../StellaOps.Policy.csproj.Backup.tmp | 32 + .../ScoringApiContractTests.cs | 6 +- ...llaOps.Policy.Engine.Contract.Tests.csproj | 20 +- .../Crypto/CryptoRiskEvaluatorTests.cs | 370 + .../PolicyEngineDeterminismTests.cs | 7 +- ...ScoreBasedRuleMonotonicityPropertyTests.cs | 1 + .../Gates/CicdGateIntegrationTests.cs | 16 +- .../VexTrustConfidenceFactorProviderTests.cs | 309 + .../Gates/VexTrustGateTests.cs | 545 + .../EwsAttestationReproducibilityTests.cs | 2 +- .../EwsPipelinePerformanceTests.cs | 1 - .../RiskBudgetMonotonicityPropertyTests.cs | 1 + .../ScoreRuleMonotonicityPropertyTests.cs | 1 + .../Properties/UnknownsBudgetPropertyTests.cs | 1 + .../VexLatticeMergePropertyTests.cs | 1 + .../Scoring/AdvancedScoringEngineTests.cs | 2 +- .../ConfidenceToEwsComparisonTests.cs | 2 +- .../Scoring/ScorePolicyServiceCachingTests.cs | 4 +- .../Scoring/SimpleScoringEngineTests.cs | 2 +- .../RiskSimulationBreakdownServiceTests.cs | 14 +- .../PolicyEvaluationTraceSnapshotTests.cs | 4 +- .../Snapshots/VerdictEwsSnapshotTests.cs | 2 +- .../StellaOps.Policy.Engine.Tests.csproj | 25 +- .../Workers/ExceptionLifecycleServiceTests.cs | 4 +- .../StellaOps.Policy.Exceptions.Tests.csproj | 14 +- .../GatewayActivationTests.cs | 7 +- .../PolicyEngineClientTests.cs | 3 +- .../PolicyGatewayDpopProofGeneratorTests.cs | 3 +- .../StellaOps.Policy.Gateway.Tests.csproj | 19 +- .../VexTrustGateIntegrationTests.cs | 695 + .../W1/PolicyGatewayIntegrationTests.cs | 4 +- .../EnvironmentOverrideTests.cs | 4 +- .../PolicyPackSchemaTests.cs | 33 +- .../StellaOps.Policy.Pack.Tests.csproj | 18 +- .../EvaluationRunRepositoryTests.cs | 7 +- .../ExceptionObjectRepositoryTests.cs | 7 +- .../ExceptionRepositoryTests.cs | 7 +- .../PackRepositoryTests.cs | 7 +- .../PackVersioningWorkflowTests.cs | 7 +- .../PolicyAuditRepositoryTests.cs | 7 +- .../PolicyMigrationTests.cs | 3 +- .../PolicyPostgresFixture.cs | 10 +- .../PolicyQueryDeterminismTests.cs | 47 +- .../PolicyVersioningImmutabilityTests.cs | 22 +- ...gresExceptionApplicationRepositoryTests.cs | 5 +- .../PostgresExceptionObjectRepositoryTests.cs | 5 +- .../PostgresReceiptRepositoryTests.cs | 9 +- .../RecheckEvidenceMigrationTests.cs | 6 +- .../RiskProfileRepositoryTests.cs | 7 +- .../RiskProfileVersionHistoryTests.cs | 7 +- .../RuleRepositoryTests.cs | 7 +- ...StellaOps.Policy.Persistence.Tests.csproj} | 26 +- .../UnknownsRepositoryTests.cs | 6 +- .../RiskProfileCanonicalizerTests.cs | 3 +- .../RiskProfileValidatorTests.cs | 4 +- .../StellaOps.Policy.RiskProfile.Tests.csproj | 7 +- .../CvssPolicyLoaderTests.cs | 3 +- .../CvssV4EngineTests.cs | 2 +- .../CvssV4EnvironmentalTests.cs | 591 + .../Fixtures/hashing/receipt-input.json | Bin 0 -> 2412 bytes .../Fixtures/hashing/receipt-input.sha256 | Bin 0 -> 134 bytes .../MacroVectorLookupTests.cs | 1 - .../StellaOps.Policy.Scoring.Tests.csproj | 16 +- .../PolicyBinderTests.cs | 3 +- .../PolicyPreviewServiceTests.cs | 1 - .../PolicyScoringConfigTests.cs | 3 +- .../PolicyValidationCliTests.cs | 3 +- .../SplLayeringEngineTests.cs | 3 +- .../SplSchemaResourceTests.cs | 3 +- .../StellaOps.Policy.Tests.csproj | 17 +- .../ClaimScoreMergerPropertyTests.cs | 1 + .../StellaOps.Policy.Unknowns.Tests.csproj | 14 +- .../PolicyDslRoundtripPropertyTests.cs | 61 +- .../StellaOps.PolicyDsl.Tests.csproj | 20 +- .../Directory.Build.props | 5 - ...ellaOps.Provenance.Attestation.Tool.csproj | 5 + .../Fixtures/cosign.sig | Bin 0 -> 182 bytes .../SampleStatementDigestTests.cs | 4 +- .../SignersTests.cs | Bin 0 -> 19768 bytes ...llaOps.Provenance.Attestation.Tests.csproj | 16 +- .../TestTimeProvider.cs | Bin 0 -> 956 bytes .../ToolEntrypointTests.cs | 7 +- .../ToolEntrypointTests.cs.utf8 | 1 + .../VerificationLibraryTests.cs | 123 + .../VerificationLibraryTests.cs.utf8 | 1 + .../Controllers/ReachGraphController.cs | 251 + .../Models/ReachGraphContracts.cs | 146 + .../Program.cs | 120 + .../Properties/launchSettings.json | 12 + .../Services/IReachGraphReplayService.cs | 16 + .../Services/IReachGraphSliceService.cs | 40 + .../Services/IReachGraphStoreService.cs | 33 + .../Services/PaginationService.cs | 194 + .../Services/ReachGraphReplayService.cs | 138 + .../Services/ReachGraphSliceService.cs | 521 + .../Services/ReachGraphStoreService.cs | 175 + .../StellaOps.ReachGraph.WebService.csproj | 27 + .../openapi.yaml | 712 + src/ReachGraph/StellaOps.ReachGraph.sln | 76 + .../ReachGraphApiIntegrationTests.cs | 305 + ...ellaOps.ReachGraph.WebService.Tests.csproj | 30 + .../Program.cs | 2 +- .../StellaOps.Registry.TokenService.csproj | 8 +- src/Registry/StellaOps.Registry.sln | 407 +- .../RegistryTokenIssuerTests.cs | 3 +- ...ellaOps.Registry.TokenService.Tests.csproj | 1 - .../Properties/launchSettings.json | 12 + .../StellaOps.Replay.WebService.csproj | 5 +- .../VerdictReplayEndpoints.cs | 333 + src/Replay/StellaOps.Replay.sln | 304 + ...PolicySimulationInputLockValidatorTests.cs | 0 .../StellaOps.Replay.Core.Tests.csproj | 27 + .../VerdictReplayEndpointsTests.cs | 294 + .../VerdictReplayIntegrationTests.cs | 416 + src/RiskEngine/StellaOps.RiskEngine.sln | 238 +- .../EpssBundleTests.cs | 2 +- .../RiskEngineApiTests.cs | 8 +- .../StellaOps.RiskEngine.Tests.csproj | 31 +- .../StellaOps.RiskEngine.WebService.csproj | 4 +- .../StellaOps.RiskEngine.Worker.csproj | 2 +- .../StellaOps.RiskEngine.sln | 90 - src/Router/AGENTS.md | 183 + .../Authorization/AuthorizationMiddleware.cs | 99 + .../Authorization/EffectiveClaimsStore.cs | 96 + .../Authorization/IEffectiveClaimsStore.cs | 15 + .../Configuration/GatewayOptions.cs | 213 + .../Configuration/GatewayOptionsValidator.cs | 39 + .../Configuration/GatewayValueParser.cs | 82 + .../StellaOps.Gateway.WebService/Dockerfile | 14 + .../Middleware/ClaimsPropagationMiddleware.cs | 88 + .../Middleware/CorrelationIdMiddleware.cs | 30 + .../Middleware/GatewayContextKeys.cs | 13 + .../Middleware/GatewayRoutes.cs | 34 + .../Middleware/HealthCheckMiddleware.cs | 89 + .../IdentityHeaderPolicyMiddleware.cs | 333 + .../Middleware/RequestRoutingMiddleware.cs | 22 + .../Middleware/SenderConstraintMiddleware.cs | 214 + .../Middleware/TenantMiddleware.cs | 40 + .../StellaOps.Gateway.WebService/Program.cs | 323 + .../Properties/launchSettings.json | 12 + .../Security/AllowAllAuthenticationHandler.cs | 30 + .../Services/GatewayHealthMonitorService.cs | 106 + .../Services/GatewayHostedService.cs | 531 + .../Services/GatewayMetrics.cs | 38 + .../Services/GatewayServiceStatus.cs | 28 + .../Services/GatewayTransportClient.cs | 253 + .../StellaOps.Gateway.WebService.csproj | 24 + .../appsettings.Development.json | 12 + .../appsettings.json | 68 + src/Router/StellaOps.Router.sln | 619 + .../InMemoryAtomicTokenStore.cs | 0 .../InMemoryCacheFactory.cs | 0 .../InMemoryCacheStore.cs | 0 .../InMemoryEventStream.cs | 0 .../InMemoryIdempotencyStore.cs | 0 .../InMemoryMessageLease.cs | 0 .../InMemoryMessageQueue.cs | 0 .../InMemoryMessageQueueFactory.cs | 0 .../InMemoryQueueRegistry.cs | 0 .../InMemoryRateLimiter.cs | 0 .../InMemorySetStore.cs | 0 .../InMemorySortedIndex.cs | 0 .../InMemoryTransportPlugin.cs | 0 ...llaOps.Messaging.Transport.InMemory.csproj | 8 +- .../Options/PostgresTransportOptions.cs | 0 .../PostgresAtomicTokenStore.cs | 0 .../PostgresCacheFactory.cs | 0 .../PostgresCacheStore.cs | 0 .../PostgresConnectionFactory.cs | 0 .../PostgresEventStream.cs | 0 .../PostgresIdempotencyStore.cs | 0 .../PostgresMessageLease.cs | 0 .../PostgresMessageQueue.cs | 0 .../PostgresMessageQueueFactory.cs | 0 .../PostgresRateLimiter.cs | 0 .../PostgresSetStore.cs | 0 .../PostgresSortedIndex.cs | 0 .../PostgresTransportPlugin.cs | 0 ...llaOps.Messaging.Transport.Postgres.csproj | 18 +- .../Options/ValkeyTransportOptions.cs | 0 ...tellaOps.Messaging.Transport.Valkey.csproj | 16 +- .../ValkeyAtomicTokenStore.cs | 0 .../ValkeyCacheFactory.cs | 0 .../ValkeyCacheStore.cs | 2 +- .../ValkeyConnectionFactory.cs | 0 .../ValkeyEventStream.cs | 0 .../ValkeyIdempotencyStore.cs | 0 .../ValkeyMessageLease.cs | 0 .../ValkeyMessageQueue.cs | 0 .../ValkeyMessageQueueFactory.cs | 0 .../ValkeyRateLimiter.cs | 0 .../ValkeySetStore.cs | 0 .../ValkeySortedIndex.cs | 0 .../ValkeyTransportPlugin.cs | 0 .../Abstractions/IAtomicTokenStore.cs | 0 .../Abstractions/IDistributedCache.cs | 0 .../Abstractions/IEventStream.cs | 0 .../Abstractions/IIdempotencyStore.cs | 0 .../Abstractions/IMessageLease.cs | 0 .../Abstractions/IMessageQueue.cs | 0 .../Abstractions/IMessageQueueFactory.cs | 0 .../Abstractions/IRateLimiter.cs | 0 .../Abstractions/ISetStore.cs | 0 .../Abstractions/ISortedIndex.cs | 0 .../MessagingServiceCollectionExtensions.cs | 0 .../Options/CacheOptions.cs | 0 .../Options/EventStreamOptions.cs | 0 .../Options/MessageQueueOptions.cs | 0 .../Options/MessagingPluginOptions.cs | 0 .../Results/CacheResult.cs | 0 .../Results/EnqueueOptions.cs | 0 .../Results/EnqueueResult.cs | 0 .../Results/EventStreamResult.cs | 0 .../Results/IdempotencyResult.cs | 0 .../Results/LeaseRequest.cs | 0 .../Results/RateLimitResult.cs | 0 .../Results/TokenResult.cs | 0 .../StellaOps.Messaging.csproj | 16 +- .../AspNetCoreEndpointDiscoveryProvider.cs | 0 .../AspNetEndpointDescriptor.cs | 0 .../AspNetEndpointOverrideMerger.cs | 0 .../AspNetRouterRequestDispatcher.cs | 0 .../DefaultAuthorizationClaimMapper.cs | 0 .../IAspNetEndpointDiscoveryProvider.cs | 0 .../IAspNetRouterRequestDispatcher.cs | 0 .../IAuthorizationClaimMapper.cs | 0 .../StellaOps.Microservice.AspNetCore.csproj | 0 .../StellaRouterBridgeExtensions.cs | 0 .../StellaRouterBridgeOptions.cs | 0 .../DiagnosticDescriptors.cs | 0 .../EndpointInfo.cs | 0 .../Polyfills.cs | 0 .../SchemaGenerator.cs | 0 .../StellaEndpointGenerator.cs | 0 .../StellaOps.Microservice.SourceGen.csproj | 4 +- .../EndpointDiscoveryService.cs | 0 .../EndpointOverrideMerger.cs | 0 .../EndpointRegistry.cs | 0 .../Endpoints/SchemaDiscoveryEndpoints.cs | 0 .../GeneratedEndpointDiscoveryProvider.cs | 0 .../HeaderCollection.cs | 0 .../IEndpointDiscoveryProvider.cs | 0 .../IEndpointRegistry.cs | 0 .../IGeneratedEndpointProvider.cs | 0 .../IHeaderCollection.cs | 0 .../IRequestDispatcher.cs | 0 .../IRouterConnectionManager.cs | 0 .../StellaOps.Microservice/IStellaEndpoint.cs | 0 .../InflightRequestTracker.cs | 0 .../MicroserviceHostedService.cs | 0 .../MicroserviceYamlConfig.cs | 0 .../MicroserviceYamlLoader.cs | 0 .../StellaOps.Microservice/PathMatcher.cs | 0 .../RawRequestContext.cs | 0 .../StellaOps.Microservice/RawResponse.cs | 0 .../ReflectionEndpointDiscoveryProvider.cs | 0 .../RequestDispatcher.cs | 0 .../RouterConnectionManager.cs | 0 .../RouterEndpointConfig.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../StellaEndpointAttribute.cs | 0 .../StellaMicroserviceOptions.cs | 0 .../StellaOps.Microservice.csproj | 20 + .../Streaming/StreamingRequestBodyStream.cs | 0 .../Streaming/StreamingResponseBodyStream.cs | 0 .../TypedEndpointAdapter.cs | 0 .../ValidateSchemaAttribute.cs | 0 .../Validation/EndpointSchemaDefinition.cs | 0 .../Validation/IGeneratedSchemaProvider.cs | 0 .../Validation/IRequestSchemaValidator.cs | 0 .../Validation/ISchemaRegistry.cs | 0 .../Validation/RequestSchemaValidator.cs | 4 +- .../Validation/SchemaDirection.cs | 0 .../Validation/SchemaRegistry.cs | 0 .../Validation/SchemaValidationError.cs | 0 .../Validation/SchemaValidationException.cs | 0 .../Validation/ValidationProblemDetails.cs | 0 .../CompositeRequestDispatcher.cs | 0 .../StellaOps.Router.AspNet.csproj | 0 .../StellaRouterExtensions.cs | 0 .../StellaRouterIntegrationHelper.cs | 0 .../StellaRouterOptions.cs | 0 .../StellaRouterOptionsBase.cs | 0 .../Abstractions/IGlobalRoutingState.cs | 0 .../Abstractions/IMicroserviceTransport.cs | 0 .../Abstractions/IRegionProvider.cs | 0 .../Abstractions/IRoutingPlugin.cs | 0 .../Abstractions/ITransportClient.cs | 0 .../Abstractions/ITransportServer.cs | 0 .../Enums/FrameType.cs | 0 .../Enums/InstanceHealthStatus.cs | 0 .../Enums/TransportType.cs | 0 .../Frames/FrameConverter.cs | 0 .../Frames/RequestFrame.cs | 0 .../Frames/ResponseFrame.cs | 0 .../Models/CancelPayload.cs | 0 .../Models/ClaimRequirement.cs | 0 .../Models/ConnectionState.cs | 0 .../Models/EndpointDescriptor.cs | 0 .../Models/EndpointSchemaInfo.cs | 0 .../StellaOps.Router.Common/Models/Frame.cs | 0 .../Models/HeartbeatPayload.cs | 0 .../Models/HelloPayload.cs | 0 .../Models/InstanceDescriptor.cs | 0 .../Models/PayloadLimits.cs | 0 .../Models/RoutingContext.cs | 0 .../Models/RoutingDecision.cs | 0 .../Models/SchemaDefinition.cs | 0 .../Models/ServiceOpenApiInfo.cs | 0 .../Models/StreamDataPayload.cs | 0 .../Models/StreamingOptions.cs | 0 .../StellaOps.Router.Common/PathMatcher.cs | 0 .../StellaOps.Router.Common.csproj} | 8 +- .../IRouterConfigProvider.cs | 0 .../StellaOps.Router.Config/RouterConfig.cs | 0 .../RouterConfigOptions.cs | 0 .../RouterConfigProvider.cs | 0 .../StellaOps.Router.Config/RoutingOptions.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../StellaOps.Router.Config/ServiceConfig.cs | 0 .../StaticInstanceConfig.cs | 0 .../StellaOps.Router.Config.csproj | 20 +- .../StellaOps.Router.Gateway/AGENTS.md | 0 .../ApplicationBuilderExtensions.cs | 0 .../AuthorityClaimsRefreshService.cs | 0 .../AuthorityConnectionOptions.cs | 0 .../Authorization/AuthorizationMiddleware.cs | 0 ...uthorizationServiceCollectionExtensions.cs | 0 .../Authorization/EffectiveClaimsStore.cs | 0 .../Authorization/EndpointKey.cs | 0 .../HttpAuthorityClaimsProvider.cs | 0 .../Authorization/IAuthorityClaimsProvider.cs | 0 .../Authorization/IEffectiveClaimsStore.cs | 0 .../Configuration/HealthOptions.cs | 0 .../Configuration/RouterNodeConfig.cs | 0 .../Configuration/RoutingOptions.cs | 0 .../RouterServiceCollectionExtensions.cs | 0 .../StellaOps.Router.Gateway/GlobalUsings.cs | 0 .../Middleware/ByteCountingStream.cs | 0 .../EndpointResolutionMiddleware.cs | 0 .../GlobalErrorHandlerMiddleware.cs | 0 .../PayloadLimitExceededException.cs | 0 .../Middleware/PayloadLimitsMiddleware.cs | 0 .../Middleware/PayloadTracker.cs | 0 .../Middleware/RequestLoggingMiddleware.cs | 0 .../Middleware/RouterErrorWriter.cs | 0 .../Middleware/RoutingDecisionMiddleware.cs | 0 .../Middleware/TransportDispatchMiddleware.cs | 0 .../OpenApi/ClaimSecurityMapper.cs | 0 .../OpenApi/IOpenApiDocumentGenerator.cs | 0 .../OpenApi/IRouterOpenApiDocumentCache.cs | 0 .../OpenApi/OpenApiAggregationOptions.cs | 0 .../OpenApi/OpenApiDocumentGenerator.cs | 0 .../OpenApi/OpenApiEndpoints.cs | 0 .../OpenApi/RouterOpenApiDocumentCache.cs | 0 .../RateLimit/CircuitBreaker.cs | 0 .../RateLimit/EnvironmentRateLimiter.cs | 0 .../RateLimit/InMemoryValkeyRateLimitStore.cs | 0 .../RateLimit/InstanceRateLimiter.cs | 0 .../RateLimit/LimitInheritanceResolver.cs | 0 .../RateLimit/RateLimitConfig.cs | 0 .../RateLimit/RateLimitDecision.cs | 0 .../RateLimit/RateLimitMetrics.cs | 0 .../RateLimit/RateLimitMiddleware.cs | 0 .../RateLimit/RateLimitRouteMatcher.cs | 0 .../RateLimit/RateLimitRule.cs | 0 .../RateLimit/RateLimitService.cs | 0 .../RateLimitServiceCollectionExtensions.cs | 0 .../RateLimit/ValkeyRateLimitStore.cs | 0 .../RouterHttpContextKeys.cs | 0 .../Routing/DefaultRoutingPlugin.cs | 0 .../Services/ConnectionManager.cs | 0 .../Services/HealthMonitorService.cs | 0 .../Services/PingTracker.cs | 0 .../State/InMemoryRoutingState.cs | 0 .../StellaOps.Router.Gateway.csproj | 4 +- .../InMemoryChannel.cs | 0 .../InMemoryConnectionRegistry.cs | 0 .../InMemoryTransportClient.cs | 0 .../InMemoryTransportOptions.cs | 0 .../InMemoryTransportPlugin.cs | 59 + .../InMemoryTransportServer.cs | 0 .../ServiceCollectionExtensions.cs | 0 ...StellaOps.Router.Transport.InMemory.csproj | 6 +- .../MessagingTransportClient.cs | 0 .../MessagingTransportServer.cs | 3 + .../Options/MessagingTransportOptions.cs | 0 .../Protocol/CorrelationTracker.cs | 0 .../Protocol/RpcRequestMessage.cs | 0 .../Protocol/RpcResponseMessage.cs | 0 .../ServiceCollectionExtensions.cs | 0 ...tellaOps.Router.Transport.Messaging.csproj | 6 +- .../RabbitMqFrameProtocol.cs | 0 .../RabbitMqTransportClient.cs | 7 +- .../RabbitMqTransportOptions.cs | 0 .../RabbitMqTransportPlugin.cs | 55 + .../RabbitMqTransportServer.cs | 7 +- .../ServiceCollectionExtensions.cs | 0 ...StellaOps.Router.Transport.RabbitMq.csproj | 8 +- .../FrameProtocol.cs | 0 .../PendingRequestTracker.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../StellaOps.Router.Transport.Tcp.csproj | 6 +- .../TcpConnection.cs | 0 .../TcpTransportClient.cs | 0 .../TcpTransportOptions.cs | 0 .../TcpTransportPlugin.cs | 55 + .../TcpTransportServer.cs | 0 .../CertificateLoader.cs | 0 .../CertificateWatcher.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../StellaOps.Router.Transport.Tls.csproj | 0 .../TlsConnection.cs | 0 .../TlsTransportClient.cs | 0 .../TlsTransportOptions.cs | 0 .../TlsTransportPlugin.cs | 55 + .../TlsTransportServer.cs | 0 .../PayloadTooLargeException.cs | 0 .../ServiceCollectionExtensions.cs | 0 .../StellaOps.Router.Transport.Udp.csproj | 6 +- .../UdpFrameProtocol.cs | 0 .../UdpTransportClient.cs | 0 .../UdpTransportOptions.cs | 0 .../UdpTransportPlugin.cs | 55 + .../UdpTransportServer.cs | 0 .../AuthorizationMiddlewareTests.cs | 265 + .../EffectiveClaimsStoreTests.cs | 272 + .../GatewayOptionsValidatorTests.cs | 161 + .../Configuration/GatewayValueParserTests.cs | 81 + .../GatewayHealthTests.cs | 4 +- .../Integration/GatewayIntegrationTests.cs | 184 + .../MessagingTransportIntegrationTests.cs | 215 + .../ClaimsPropagationMiddlewareTests.cs | 153 + .../CorrelationIdMiddlewareTests.cs | 70 + .../Middleware/GatewayRoutesTests.cs | 93 + .../IdentityHeaderPolicyMiddlewareTests.cs | 502 + .../Middleware/TenantMiddlewareTests.cs | 110 + .../StellaOps.Gateway.WebService.Tests.csproj | 42 + .../xunit.runner.json | 5 + .../AtLeastOnceDeliveryTests.cs | 0 .../Fixtures/ValkeyContainerFixture.cs | 0 .../ValkeyIntegrationFactAttribute.cs | 0 ...ps.Messaging.Transport.Valkey.Tests.csproj | 26 +- .../ValkeyTransportComplianceTests.cs | 3 +- .../StellaEndpointGeneratorTests.cs | 2 +- ...llaOps.Microservice.SourceGen.Tests.csproj | 39 + .../EndpointDiscoveryServiceTests.cs | 0 .../EndpointRegistryTests.cs | 0 .../HeaderCollectionTests.cs | 0 .../InflightRequestTrackerTests.cs | 3 +- .../RawRequestContextTests.cs | 3 +- .../RawResponseTests.cs | 3 +- .../RouterConnectionManagerTests.cs | 6 +- .../StellaOps.Microservice.Tests.csproj | 29 +- .../Validation/RequestSchemaValidatorTests.cs | 0 .../Validation/SchemaRegistryTests.cs | 0 .../ValidationProblemDetailsTests.cs | 0 .../FrameConverterTests.cs | 0 .../MessageFramingRoundTripTests.cs | 4 +- .../PathMatcherTests.cs | 0 .../RoutingDeterminismTests.cs | 0 .../RoutingRulesEvaluationTests.cs | 0 .../StellaOps.Router.Common.Tests.csproj | 20 +- .../ConfigChangedEventArgsTests.cs | 0 .../ConfigValidationResultTests.cs | 0 .../RouterConfigOptionsTests.cs | 0 .../RouterConfigProviderTests.cs | 0 .../RouterConfigTests.cs | 0 .../RoutingOptionsTests.cs | 0 .../ServiceConfigTests.cs | 0 .../StaticInstanceConfigTests.cs | 0 .../StellaOps.Router.Config.Tests.csproj | 20 +- .../ConnectionManagerIntegrationTests.cs | 4 +- .../EndToEndRoutingTests.cs | 0 .../EndpointRegistryIntegrationTests.cs | 0 .../MicroserviceIntegrationFixture.cs | 0 .../Fixtures/TestEndpoints.cs | 2 +- .../MessageOrderingTests.cs | 29 +- .../ParameterBindingTests.cs | 0 .../PathMatchingIntegrationTests.cs | 0 .../RequestDispatchIntegrationTests.cs | 2 +- .../ServiceRegistrationIntegrationTests.cs | 3 +- .../StellaOps.Router.Integration.Tests.csproj | 38 + .../TransportIntegrationTests.cs | 0 .../BackpressureTests.cs | 5 +- .../InMemoryChannelTests.cs | 3 +- .../InMemoryConnectionRegistryTests.cs | 0 .../InMemoryTransportComplianceTests.cs | 8 +- .../InMemoryTransportOptionsTests.cs | 0 ...Ops.Router.Transport.InMemory.Tests.csproj | 20 +- .../RouterTransportPluginLoaderTests.cs | 411 + ...laOps.Router.Transport.Plugin.Tests.csproj | 45 + .../TransportPluginRegistrationTests.cs | 443 + .../Fixtures/RabbitMqContainerFixture.cs | 0 .../RabbitMqIntegrationFactAttribute.cs | 0 .../RabbitMqFrameProtocolTests.cs | 0 .../RabbitMqIntegrationTests.cs | 0 .../RabbitMqTransportClientTests.cs | 2 +- .../RabbitMqTransportComplianceTests.cs | 0 .../RabbitMqTransportOptionsTests.cs | 0 .../RabbitMqTransportServerTests.cs | 2 +- ...Ops.Router.Transport.RabbitMq.Tests.csproj | 24 +- .../ConnectionFailureTests.cs | 5 +- .../FrameFuzzTests.cs | 5 +- ...tellaOps.Router.Transport.Tcp.Tests.csproj | 32 + .../TcpTransportComplianceTests.cs | 5 +- .../TcpTransportTests.cs | 3 +- ...tellaOps.Router.Transport.Tls.Tests.csproj | 27 + .../TlsTransportComplianceTests.cs | 7 +- .../TlsTransportTests.cs | 3 +- ...tellaOps.Router.Transport.Udp.Tests.csproj | 28 + .../UdpFrameProtocolTests.cs | 0 .../UdpTransportClientTests.cs | 3 +- .../UdpTransportOptionsTests.cs | 0 .../UdpTransportServerTests.cs | 3 +- .../Builders/TestMessageBuilder.cs | 0 .../Fixtures/InMemoryMessagingFixture.cs | 0 .../Fixtures/PostgresQueueFixture.cs | 0 .../Fixtures/ValkeyFixture.cs | 2 +- .../StellaOps.Messaging.Testing.csproj | 28 + .../Factories/TestFrameFactory.cs | 0 .../Fixtures/RouterTestFixture.cs | 0 .../Mocks/MockConnectionState.cs | 0 .../Mocks/RecordingLogger.cs | 0 .../StellaOps.Router.Testing.csproj | 10 +- .../Endpoints/CreateInvoiceEndpoint.cs | 70 + .../Endpoints/GetInvoiceEndpoint.cs | 58 + .../Endpoints/UploadAttachmentEndpoint.cs | 60 + .../Examples.Billing.Microservice.csproj | 27 + .../Examples.Billing.Microservice/Program.cs | 40 + .../microservice.yaml | 21 + .../Examples.Gateway/Examples.Gateway.csproj | 18 + .../examples/Examples.Gateway/Program.cs | 50 + .../Properties/launchSettings.json | 12 + .../Examples.Gateway/appsettings.json | 13 + .../examples/Examples.Gateway/router.yaml | 50 + .../Endpoints/GetItemEndpoint.cs | 64 + .../Endpoints/ListItemsEndpoint.cs | 107 + .../Examples.Inventory.Microservice.csproj | 23 + .../Program.cs | 38 + .../Examples.MultiTransport.Gateway.csproj | 26 + .../Program.cs | 101 + .../Properties/launchSettings.json | 12 + .../appsettings.json | 23 + .../router.yaml | 95 + .../BroadcastNotificationEndpoint.cs | 221 + .../Endpoints/GetNotificationsEndpoint.cs | 150 + .../MarkNotificationsReadEndpoint.cs | 96 + .../Endpoints/SendNotificationEndpoint.cs | 128 + .../SendTemplatedNotificationEndpoint.cs | 194 + .../SubscribeNotificationsEndpoint.cs | 256 + .../Endpoints/UpdatePreferencesEndpoint.cs | 133 + .../Examples.NotificationService.csproj | 40 + .../Models/Notification.cs | 171 + .../Examples.NotificationService/Program.cs | 100 + .../microservice.yaml | 185 + .../Endpoints/CancelOrderEndpoint.cs | 95 + .../Endpoints/CreateOrderEndpoint.cs | 124 + .../Endpoints/ExportOrdersEndpoint.cs | 112 + .../Endpoints/GetOrderEndpoint.cs | 126 + .../Endpoints/ListOrdersEndpoint.cs | 117 + .../Endpoints/OrderEventsEndpoint.cs | 194 + .../Endpoints/UpdateOrderStatusEndpoint.cs | 116 + .../Endpoints/UploadOrderDocumentEndpoint.cs | 134 + .../Examples.OrderService.csproj | 30 + .../Examples.OrderService/Models/Order.cs | 111 + .../examples/Examples.OrderService/Program.cs | 117 + .../Examples.OrderService/microservice.yaml | 47 + ....SbomService.Storage.Postgres.Tests.csproj | 35 - ...llaOps.SbomService.Storage.Postgres.csproj | 13 - .../OrchestratorEndpointsTests.cs | 2 +- .../ResolverFeedExportTests.cs | 2 +- .../SbomEndpointsTests.cs | 2 +- .../SbomEventEndpointsTests.cs | 2 +- .../SbomInventoryEventsTests.cs | 2 +- .../SbomLedgerEndpointsTests.cs | 3 +- .../StellaOps.SbomService.Tests.csproj | 9 +- src/SbomService/StellaOps.SbomService.sln | 686 +- .../Models/ReplayVerificationModels.cs | 82 + .../Models/SbomLedgerModels.cs | 174 +- .../StellaOps.SbomService/Program.cs | 332 + .../Properties/launchSettings.json | 12 + .../Repositories/ISbomLedgerRepository.cs | 2 + .../ISbomLineageEdgeRepository.cs | 114 + .../InMemorySbomLedgerRepository.cs | 18 + .../InMemorySbomLineageEdgeRepository.cs | 244 + .../Services/ILineageCompareCache.cs | 112 + .../Services/ILineageCompareService.cs | 532 + .../Services/IReplayHashService.cs | 102 + .../Services/IReplayVerificationService.cs | 255 + .../Services/ISbomLineageGraphService.cs | 100 + .../Services/InMemoryLineageCompareCache.cs | 324 + .../Services/LineageCompareService.cs | 528 + .../Services/LineageHoverCache.cs | 309 + .../Services/ReplayHashService.cs | 272 + .../Services/ReplayVerificationService.cs | 348 + .../Services/SbomLedgerService.cs | 131 +- .../Services/SbomLineageGraphService.cs | 539 + .../Services/SbomUploadService.cs | 7 +- .../StellaOps.SbomService.csproj | 2 + .../EfCore/Context/SbomServiceDbContext.cs | 21 + .../SbomServicePersistenceExtensions.cs} | 24 +- .../Repositories/PostgresCatalogRepository.cs | 2 +- .../PostgresComponentLookupRepository.cs | 2 +- .../PostgresEntrypointRepository.cs | 2 +- .../PostgresOrchestratorControlRepository.cs | 2 +- .../PostgresOrchestratorRepository.cs | 2 +- .../PostgresProjectionRepository.cs | 2 +- .../PostgresSbomLineageEdgeRepository.cs | 342 + .../PostgresSbomVerdictLinkRepository.cs | 340 + .../Postgres}/SbomServiceDataSource.cs | 2 +- .../ISbomLineageEdgeRepository.cs | 178 + .../ISbomVerdictLinkRepository.cs | 120 + .../StellaOps.SbomService.Persistence.csproj | 26 + .../PostgresEntrypointRepositoryTests.cs | 5 +- ...tgresOrchestratorControlRepositoryTests.cs | 7 +- .../SbomServicePostgresFixture.cs | 3 +- ...laOps.SbomService.Persistence.Tests.csproj | 27 + src/Scanner/AGENTS.md | 2 +- .../StellaOps.Scanner.Analyzers.Native.csproj | 10 +- .../StellaOps.Scanner.Node.Phase22.slnf | 10 - .../StellaOps.Scanner.Node.Phase22.slnx | 2 - src/Scanner/StellaOps.Scanner.Node.slnf | 15 - ...ellaOps.Scanner.Sbomer.BuildXPlugin.csproj | 6 +- .../Contracts/SbomContracts.cs | 62 + .../Endpoints/EpssEndpoints.cs | 2 + .../Endpoints/ReachabilityStackEndpoints.cs | 2 +- .../Options/ScannerWebServiceOptions.cs | 66 + .../StellaOps.Scanner.WebService/Program.cs | 5 +- .../Properties/launchSettings.json | 12 + .../Serialization/CborNegotiation.cs | 2 +- .../Services/AttestationChainVerifier.cs | 2 - .../Services/DeterministicScoringService.cs | 2 +- .../HumanApprovalAttestationService.cs | 6 - .../Services/IOciAttestationPublisher.cs | 75 + .../Services/NullOciAttestationPublisher.cs | 35 + .../Services/NullOfflineKitAuditEmitter.cs | 4 +- .../Services/OciAttestationPublisher.cs | 270 + .../Services/OfflineAttestationVerifier.cs | 2 +- .../Services/OfflineKitImportService.cs | 4 +- .../Services/ReplayCommandService.cs | 2 +- .../Services/SbomByosUploadService.cs | 11 + .../StellaOps.Scanner.WebService.csproj | 18 +- .../BinaryIndexServiceExtensions.cs | 9 +- .../Processing/BinaryFindingMapper.cs | 15 +- .../StellaOps.Scanner.Worker/Program.cs | 3 +- .../StellaOps.Scanner.Worker.csproj | 11 +- src/Scanner/StellaOps.Scanner.sln | 3181 ++- ...nner.Analyzers.Lang.Deno.Benchmarks.csproj | 2 +- ...anner.Analyzers.Lang.Php.Benchmarks.csproj | 2 +- ...nner.Analyzers.Lang.Rust.Benchmarks.csproj | 2 +- ...StellaOps.Scanner.Storage.Epss.Perf.csproj | 2 +- .../StellaOps.Scanner.Advisory.csproj | 8 +- .../Internal/Crypto/DotNetCryptoExtractor.cs | 358 + ...laOps.Scanner.Analyzers.Lang.DotNet.csproj | 1 + .../Internal/Crypto/JavaCryptoExtractor.cs | 488 + ...ellaOps.Scanner.Analyzers.Lang.Java.csproj | 1 + .../Internal/Crypto/NodeCryptoExtractor.cs | 576 + ...ellaOps.Scanner.Analyzers.Lang.Node.csproj | 3 +- .../StellaOps.Scanner.Analyzers.OS.Apk.csproj | 2 +- ...StellaOps.Scanner.Analyzers.OS.Dpkg.csproj | 2 +- ...laOps.Scanner.Analyzers.OS.Homebrew.csproj | 2 +- ...ps.Scanner.Analyzers.OS.MacOsBundle.csproj | 4 +- ...llaOps.Scanner.Analyzers.OS.Pkgutil.csproj | 4 +- .../StellaOps.Scanner.Analyzers.OS.Rpm.csproj | 4 +- ...ner.Analyzers.OS.Windows.Chocolatey.csproj | 2 +- ...ps.Scanner.Analyzers.OS.Windows.Msi.csproj | 2 +- ...Scanner.Analyzers.OS.Windows.WinSxS.csproj | 2 +- .../StellaOps.Scanner.Analyzers.OS.csproj | 2 +- .../StellaOps.Scanner.Benchmark.csproj | 3 +- .../StellaOps.Scanner.Benchmarks.csproj | 3 - .../StellaOps.Scanner.Cache.csproj | 16 +- .../Binary/BinaryCallGraphExtractor.cs | 19 +- .../Disassembly/BinaryTextSectionReader.cs | 17 +- .../Binary/FunctionBoundaryDetector.cs | 13 +- .../DotNet/DotNetCallGraphExtractor.cs | 107 +- .../Extraction/GuardDetector.cs | 249 + .../Extraction/Java/JavaCallGraphExtractor.cs | 71 +- .../Extraction/Java/JavaModels.cs | 6 + .../Extraction/Node/BabelResultParser.cs | 12 + .../Extraction/Node/NodeCallGraphExtractor.cs | 72 +- .../Python/PythonCallGraphExtractor.cs | 43 +- .../Models/CallGraphModels.cs | 132 +- .../StellaOps.Scanner.CallGraph.csproj | 22 +- .../StellaOps.Scanner.Core.csproj | 4 +- .../StellaOps.Scanner.Emit/AGENTS.md | 42 + .../Cbom/CbomAggregationService.cs | 364 + .../Cbom/CbomSerializer.cs | 373 + .../Cbom/CryptoProperties.cs | 467 + .../Cbom/ICryptoAssetExtractor.cs | 196 + .../Composition/CycloneDxCbomWriter.cs | 508 + .../StellaOps.Scanner.Emit.csproj | 4 +- .../StellaOps.Scanner.EntryTrace.csproj | 10 +- .../FuncProofBuilder.cs | 9 + .../FuncProofTransparencyService.cs | 1 + .../SbomFuncProofLinker.cs | 8 +- .../StellaOps.Scanner.Evidence.csproj | 4 +- .../StellaOps.Scanner.Explainability.csproj | 2 +- .../StellaOps.Scanner.Orchestration.csproj | 2 +- .../StellaOps.Scanner.ProofIntegration.csproj | 2 +- .../StellaOps.Scanner.ProofSpine.csproj | 2 +- .../StellaOps.Scanner.Queue.csproj | 20 +- .../Runtime/EbpfSignalMerger.cs | 492 + .../SinkTaxonomy.cs | 4 + .../StellaOps.Scanner.Reachability.csproj | 7 +- ...StellaOps.Scanner.ReachabilityDrift.csproj | 8 +- .../Configuration/CliSourceConfig.cs | 124 + .../Configuration/DockerSourceConfig.cs | 95 + .../Configuration/GitSourceConfig.cs | 183 + .../Configuration/ISourceConfigValidator.cs | 47 + .../Configuration/SourceConfigValidator.cs | 525 + .../Configuration/ZastavaSourceConfig.cs | 147 + .../Contracts/SourceContracts.cs | 348 + .../Domain/SbomSource.cs | 406 + .../Domain/SbomSourceEnums.cs | 85 + .../Domain/SbomSourceRun.cs | 169 + .../Persistence/ISbomSourceRepository.cs | 112 + .../Persistence/SbomSourceRepository.cs | 434 + .../Persistence/SbomSourceRunRepository.cs | 307 + .../Persistence/ScannerSourcesDataSource.cs | 22 + .../Services/ISbomSourceService.cs | 124 + .../Services/ISourceConnectionTester.cs | 43 + .../Services/SbomSourceService.cs | 422 + .../Services/SourceConnectionTester.cs | 85 + .../StellaOps.Scanner.Sources.csproj | 23 + .../FuncProofOciPublisher.cs | 6 +- .../IOciAncestryExtractor.cs | 148 + .../OciAncestryExtractor.cs | 297 + .../StellaOps.Scanner.Storage.Oci.csproj | 4 +- .../Epss/EpssCsvStreamParser.cs | 6 +- .../Migrations/001_initial_schema.sql | 1562 ++ .../Migrations/_archived/pre_1.0/README.md | 47 + .../StellaOps.Scanner.Storage.csproj | 18 +- .../StellaOps.Scanner.Surface.Env.csproj | 6 +- .../StellaOps.Scanner.Surface.FS.csproj | 10 +- .../StellaOps.Scanner.Surface.Secrets.csproj | 4 +- ...tellaOps.Scanner.Surface.Validation.csproj | 6 +- .../StellaOps.Scanner.Surface.csproj | 6 +- .../StellaOps.Scanner.Triage.csproj | 8 +- .../CecilMethodFingerprinterTests.cs | 3 +- .../NuGetPackageDownloaderTests.cs | 3 +- ...tellaOps.Scanner.VulnSurfaces.Tests.csproj | 12 +- .../VulnSurfaceIntegrationTests.cs | 4 +- .../StellaOps.Scanner.VulnSurfaces.csproj | 14 +- .../AdvisoryClientTests.cs | 3 +- .../StellaOps.Scanner.Advisory.Tests.csproj | 13 +- .../Bun/BunLanguageAnalyzerTests.cs | 36 +- ...ps.Scanner.Analyzers.Lang.Bun.Tests.csproj | 12 +- .../Deno/DenoLanguageAnalyzerRuntimeTests.cs | 2 +- .../Deno/DenoRuntimeShimTests.cs | 4 +- .../Deno/DenoRuntimeTraceRunnerTests.cs | 4 +- .../Golden/DenoAnalyzerGoldenTests.cs | 2 +- ...s.Scanner.Analyzers.Lang.Deno.Tests.csproj | 11 +- .../DotNet/Config/GlobalJsonParserTests.cs | 4 +- .../DotNet/Config/NuGetConfigParserTests.cs | 2 +- .../Parsing/MsBuildProjectParserTests.cs | 4 +- .../Parsing/PackagesConfigParserTests.cs | 4 +- ...Scanner.Analyzers.Lang.DotNet.Tests.csproj | 15 +- .../Go/GoLanguageAnalyzerTests.cs | 10 +- ...Ops.Scanner.Analyzers.Lang.Go.Tests.csproj | 9 +- .../Java/JavaEntrypointResolverTests.cs | 18 +- .../Java/JavaJniAnalyzerTests.cs | 12 +- .../Java/JavaLanguageAnalyzerTests.cs | 42 +- .../Java/JavaReflectionAnalyzerTests.cs | 8 +- .../Java/JavaServiceProviderScannerTests.cs | 6 +- .../JavaSignatureManifestAnalyzerTests.cs | 12 +- .../Java/Parsers/GradleGroovyParserTests.cs | 6 +- .../Java/Parsers/GradleKotlinParserTests.cs | 24 +- .../Parsers/GradlePropertiesParserTests.cs | 4 +- .../GradleVersionCatalogParserTests.cs | 26 +- .../Java/Parsers/MavenBomImporterTests.cs | 16 +- .../Parsers/MavenEffectivePomBuilderTests.cs | 18 +- .../Java/Parsers/MavenLocalRepositoryTests.cs | 4 +- .../Java/Parsers/MavenParentResolverTests.cs | 22 +- .../Java/Parsers/MavenPomParserTests.cs | 10 +- .../Java/Parsers/ShadedJarDetectorTests.cs | 8 +- ...s.Scanner.Analyzers.Lang.Java.Tests.csproj | 15 +- .../Phase22SmokeTests.cs | 2 +- ...nner.Analyzers.Lang.Node.SmokeTests.csproj | 9 +- .../Node/NodeDeterminismTests.cs | 2 +- .../Node/NodeEdgeCaseAndErrorTests.cs | 2 +- .../Node/NodeEntrypointDetectionTests.cs | 2 +- .../Node/NodeLanguageAnalyzerTests.cs | 34 +- .../NodePackageCollectorTraversalTests.cs | 2 +- ...s.Scanner.Analyzers.Lang.Node.Tests.csproj | 12 +- .../Php/PhpLanguageAnalyzerTests.cs | 14 +- ...ps.Scanner.Analyzers.Lang.Php.Tests.csproj | 11 +- .../PythonCapabilityDetectorTests.cs | 10 +- .../PythonEntrypointDiscoveryTests.cs | 20 +- .../Fixtures/PythonFixtureTests.cs | 14 +- .../Framework/PythonFrameworkDetectorTests.cs | 26 +- .../Imports/PythonImportGraphTests.cs | 24 +- .../PythonObservationSerializerTests.cs | 4 +- .../Packaging/PythonPackageDiscoveryTests.cs | 16 +- .../Python/PythonLanguageAnalyzerTests.cs | 48 +- .../Resolver/PythonModuleResolverTests.cs | 20 +- ...Scanner.Analyzers.Lang.Python.Tests.csproj | 9 +- .../Vendoring/VendoredPackageDetectorTests.cs | 24 +- .../PythonInputNormalizerTests.cs | 30 +- .../RubyLanguageAnalyzerTests.cs | 23 +- ...s.Scanner.Analyzers.Lang.Ruby.Tests.csproj | 13 +- .../Core/LanguageAnalyzerContextTests.cs | 4 +- .../LanguageAnalyzerHarnessTests.cs | 2 +- .../DotNet/DotNetEntrypointResolverTests.cs | 4 +- .../DotNet/DotNetLanguageAnalyzerTests.cs | 18 +- .../source-tree-only/Directory.Packages.props | 9 +- .../dotnet/source-tree-only/Sample.App.csproj | 6 +- .../Lang/Ruby/RubyLanguageAnalyzerTests.cs | 6 +- .../RustHeuristicCoverageComparisonTests.cs | 2 +- .../Rust/RustLanguageAnalyzerTests.cs | 8 +- ...llaOps.Scanner.Analyzers.Lang.Tests.csproj | 18 +- .../ElfDynamicSectionParserTests.cs | 3 +- .../Fixtures/NativeFixtureTests.cs | 2 +- .../Hardening/ElfHardeningExtractorTests.cs | 2 +- .../HardeningScoreCalculatorTests.cs | 2 +- .../Hardening/PeHardeningExtractorTests.cs | 2 +- .../HeuristicScannerTests.cs | 3 +- .../MachOLoadCommandParserTests.cs | 2 +- .../MachOReaderTests.cs | 3 +- .../NativeFormatDetectorTests.cs | 3 +- .../NativeObservationTests.cs | 3 +- .../PeImportParserTests.cs | 6 +- .../PeReaderTests.cs | 3 +- .../PluginPackagingTests.cs | 3 +- .../RuntimeCaptureTests.cs | 2 +- ...aOps.Scanner.Analyzers.Native.Tests.csproj | 25 +- .../HomebrewReceiptParserTests.cs | 3 +- ...Scanner.Analyzers.OS.Homebrew.Tests.csproj | 15 +- ...nner.Analyzers.OS.MacOsBundle.Tests.csproj | 15 +- ....Scanner.Analyzers.OS.Pkgutil.Tests.csproj | 15 +- .../OsAnalyzerDeterminismTests.cs | 3 +- ...tellaOps.Scanner.Analyzers.OS.Tests.csproj | 13 +- .../ChocolateyPackageAnalyzerTests.cs | 3 +- ...alyzers.OS.Windows.Chocolatey.Tests.csproj | 15 +- ...nner.Analyzers.OS.Windows.Msi.Tests.csproj | 15 +- ...r.Analyzers.OS.Windows.WinSxS.Tests.csproj | 15 +- .../CorpusRunnerIntegrationTests.cs | 5 + .../StellaOps.Scanner.Benchmarks.Tests.csproj | 17 +- .../LayerCacheRoundTripTests.cs | 1 - .../StellaOps.Scanner.Cache.Tests.csproj | 4 +- .../BenchmarkIntegrationTests.cs | 2 +- .../BinaryCallGraphExtractorTests.cs | 64 +- .../BinaryDisassemblyTests.cs | 9 +- .../BinaryTextSectionReaderTests.cs | 34 +- .../CallGraphExtractorRegistryTests.cs | 20 +- .../DeterminismVerificationTests.cs | 7 - .../DotNetCallGraphExtractorTests.cs | 17 +- .../GoCallGraphExtractorTests.cs | 18 +- .../JavaCallGraphExtractorTests.cs | 17 +- .../JavaScriptCallGraphExtractorTests.cs | 17 +- .../PythonCallGraphExtractorTests.cs | 106 +- .../StellaOps.Scanner.CallGraph.Tests.csproj | 14 +- .../ValkeyCallGraphCacheServiceTests.cs | 14 +- .../ReachabilityGraphBuilderUnionTests.cs | 3 +- .../ReachabilityUnionPublisherTests.cs | 3 +- .../ReachabilityUnionWriterTests.cs | 2 +- .../StellaOps.Scanner.Core.Tests.csproj | 6 +- .../ComponentDifferTests.cs | 3 +- ...tellaOps.Scanner.Emit.Lineage.Tests.csproj | 9 +- .../Cbom/CbomSerializerTests.cs | 299 + .../Cbom/CbomTests.cs | 368 + .../StellaOps.Scanner.Emit.Tests.csproj | 14 +- .../EntryTraceAnalyzerTests.cs | 3 +- .../EntryTraceNdjsonWriterTests.cs | 3 +- .../LayeredRootFileSystemTests.cs | 3 +- .../StellaOps.Scanner.EntryTrace.Tests.csproj | 14 +- .../FuncProofBuilderTests.cs | 319 +- .../FuncProofDsseServiceTests.cs | 23 +- .../SbomFuncProofLinkerTests.cs | 18 +- .../StellaOps.Scanner.Evidence.Tests.csproj | 22 +- ...llaOps.Scanner.Explainability.Tests.csproj | 13 +- .../PoEPipelineTests.cs | 16 +- ...StellaOps.Scanner.Integration.Tests.csproj | 21 +- .../TrustLattice/TrustLatticeE2ETests.cs | 10 +- .../PostgresProofSpineRepositoryTests.cs | 7 +- .../ProofSpineBuilderTests.cs | 4 +- .../StellaOps.Scanner.ProofSpine.Tests.csproj | 8 +- .../StellaOps.Scanner.Queue.Tests.csproj | 2 +- ...ps.Scanner.Reachability.Stack.Tests.csproj | 9 +- .../AttestingRichGraphWriterTests.cs | 1 - .../IncrementalCacheBenchmarkTests.cs | 14 +- .../ReachabilityPerformanceSmokeTests.cs | 68 +- .../BinaryReachabilityLifterTests.cs | 3 +- .../MiniMap/MiniMapExtractorTests.cs | 2 +- .../ReachabilityGraphPropertyTests.cs | 9 +- .../ReachabilityCacheTests.cs | 10 +- .../ReachabilityUnionPublisherTests.cs | 3 +- .../ReachabilityUnionWriterTests.cs | 3 +- .../ReachabilityWitnessDsseBuilderTests.cs | 4 +- ...abilityWitnessPublisherIntegrationTests.cs | 2 +- .../RichGraphPublisherTests.cs | 3 +- .../RichGraphWriterTests.cs | 3 +- .../ReachabilityEvidenceSnapshotTests.cs | 81 +- ...tellaOps.Scanner.Reachability.Tests.csproj | 19 +- .../SubgraphExtractorTests.cs | 2 +- ...urfaceAwareReachabilityIntegrationTests.cs | 2 +- ...Ops.Scanner.ReachabilityDrift.Tests.csproj | 19 +- .../HardeningIntegrationTests.cs | 2 +- .../DeltaVerdictAttestationTests.cs | 97 +- .../PredicateGoldenFixtureTests.cs | 3 +- .../Properties/SmartDiffPropertyTests.cs | 87 +- .../ReachabilityGateTests.cs | 3 +- .../SarifOutputGeneratorTests.cs | 2 +- .../StellaOps.Scanner.SmartDiff.Tests.csproj | 22 +- .../OciArtifactPusherTests.cs | 3 +- ...StellaOps.Scanner.Storage.Oci.Tests.csproj | 11 +- .../VerdictE2ETests.cs | 3 +- .../VerdictOciPublisherIntegrationTests.cs | 3 +- .../VerdictOciPublisherTests.cs | 3 +- .../BinaryEvidenceServiceTests.cs | 3 +- .../EpssCsvStreamParserTests.cs | 2 +- .../EpssRepositoryIntegrationTests.cs | 1 - .../StellaOps.Scanner.Storage.Tests.csproj | 8 +- .../StorageDualWriteFixture.cs | 3 +- ...StellaOps.Scanner.Surface.Env.Tests.csproj | 6 +- .../FileSurfaceManifestStoreTests.cs | 3 +- .../StellaOps.Scanner.Surface.FS.Tests.csproj | 6 +- .../CasAccessSecretParserTests.cs | 3 +- .../RegistryAccessSecretParserTests.cs | 3 +- ...laOps.Scanner.Surface.Secrets.Tests.csproj | 6 +- ...SecretsServiceCollectionExtensionsTests.cs | 3 +- .../StellaOps.Scanner.Surface.Tests.csproj | 9 +- ...ps.Scanner.Surface.Validation.Tests.csproj | 6 +- .../SurfaceValidatorRunnerTests.cs | 8 +- .../ExploitPathGroupingServiceTests.cs | 68 +- .../StellaOps.Scanner.Triage.Tests.csproj | 12 +- .../TriageQueryPerformanceTests.cs | 10 +- .../TriageSchemaIntegrationTests.cs | 4 +- .../ActionablesEndpointsTests.cs | 3 +- .../ApprovalEndpointsTests.cs | 2 +- .../AuthorizationTests.cs | 3 +- .../BaselineEndpointsTests.cs | 3 +- .../Benchmarks/TtfsPerformanceBenchmarks.cs | 2 +- .../CallGraphEndpointsTests.cs | 3 +- .../CounterfactualEndpointsTests.cs | 3 +- .../DeltaCompareEndpointsTests.cs | 3 +- .../EvidenceCompositionServiceTests.cs | 3 +- .../FindingsEvidenceControllerTests.cs | 3 +- .../GatingContractsSerializationTests.cs | 2 +- .../GatingReasonServiceTests.cs | 6 +- .../HealthEndpointsTests.cs | 3 +- .../IdempotencyMiddlewareTests.cs | 3 +- .../ManifestEndpointsTests.cs | 3 +- .../OfflineKitEndpointsTests.cs | 23 +- ...PlatformEventPublisherRegistrationTests.cs | 3 +- .../PolicyEndpointsTests.cs | 3 +- .../ProofSpineEndpointsTests.cs | 3 +- .../RateLimitingTests.cs | 3 +- .../ReachabilityDriftEndpointsTests.cs | 3 +- .../ReportSamplesTests.cs | 3 +- .../ReportsEndpointsTests.cs | 3 +- .../RubyPackagesEndpointsTests.cs | 3 +- .../RuntimeEndpointsTests.cs | 3 +- .../RuntimeReconciliationTests.cs | 5 +- .../SbomEndpointsTests.cs | 3 +- .../SbomUploadEndpointsTests.cs | 3 +- .../ScannerSurfaceSecretConfiguratorTests.cs | 3 +- .../ScansEndpointsTests.Entropy.cs | 3 +- .../ScansEndpointsTests.RecordMode.cs | 3 +- .../ScansEndpointsTests.Replay.cs | 3 +- .../ScansEndpointsTests.cs | 3 +- .../SliceEndpointsTests.cs | 3 +- .../StellaOps.Scanner.WebService.Tests.csproj | 24 +- .../TriageStatusEndpointsTests.cs | 3 +- .../CompositeScanAnalyzerDispatcherTests.cs | 3 +- .../HmacDsseEnvelopeSignerTests.cs | 3 +- .../Integration/WorkerIdempotencyTests.cs | 2 +- .../LeaseHeartbeatServiceTests.cs | 3 +- .../PoE/PoEOrchestratorDirectTests.cs | 2 +- .../RedisWorkerSmokeTests.cs | 3 +- .../RegistrySecretStageExecutorTests.cs | 3 +- ...erStorageSurfaceSecretConfiguratorTests.cs | 3 +- .../StellaOps.Scanner.Worker.Tests.csproj | 4 +- .../SurfaceManifestStageExecutorTests.cs | 3 +- .../WorkerBasicScanScenarioTests.cs | 3 +- src/Scheduler/AGENTS.md | 2 +- .../FailureSignatureEndpoints.cs | 4 +- .../GraphJobs/PostgresGraphJobStore.cs | 2 +- .../PolicyRuns/PolicyRunService.cs | 2 +- .../PolicySimulationMetricsProvider.cs | 2 +- .../StellaOps.Scheduler.WebService/Program.cs | 10 +- .../Properties/launchSettings.json | 12 + .../Runs/InMemoryRunRepository.cs | 2 +- .../Runs/RunEndpoints.cs | 2 +- .../Runs/RunStreamCoordinator.cs | 2 +- .../SchedulerEndpointHelpers.cs | 2 +- .../Schedules/InMemorySchedulerServices.cs | 2 +- .../Schedules/ScheduleContracts.cs | 2 +- .../Schedules/ScheduleEndpoints.cs | 2 +- .../StellaOps.Scheduler.WebService.csproj | 11 +- .../Program.cs | 8 +- .../StellaOps.Scheduler.Worker.Host.csproj | 2 +- src/Scheduler/StellaOps.Scheduler.sln | 1317 +- .../Tools/Scheduler.Backfill/Program.cs | 4 +- .../Scheduler.Backfill.csproj | 4 +- .../StellaOps.Scheduler.ImpactIndex.csproj | 6 +- .../EfCore/Context/SchedulerDbContext.cs | 32 + .../SchedulerPersistenceExtensions.cs} | 18 +- .../Migrations/001_initial_schema.sql | 596 + .../_archived/pre_1.0}/001_initial_schema.sql | 0 .../_archived/pre_1.0}/002_graph_jobs.sql | 0 .../_archived/pre_1.0}/003_runs_policy.sql | 0 .../pre_1.0}/010_generated_columns_runs.sql | 0 .../_archived/pre_1.0}/011_enable_rls.sql | 0 .../pre_1.0}/012_partition_audit.sql | 0 .../pre_1.0}/012b_migrate_audit_data.sql | 0 .../Postgres}/CanonicalJsonSerializer.cs | 2 +- .../Models/FailureSignatureEntity.cs | 2 +- .../Postgres}/Models/JobEntity.cs | 2 +- .../Postgres}/Models/JobHistoryEntity.cs | 2 +- .../Postgres}/Models/LockEntity.cs | 2 +- .../Postgres}/Models/MetricsEntity.cs | 2 +- .../Postgres}/Models/TriggerEntity.cs | 2 +- .../Postgres}/Models/WorkerEntity.cs | 2 +- .../Repositories/DistributedLockRepository.cs | 4 +- .../FailureSignatureRepository.cs | 4 +- .../Repositories/GraphJobRepository.cs | 2 +- .../IDistributedLockRepository.cs | 4 +- .../IFailureSignatureRepository.cs | 4 +- .../Repositories/IGraphJobRepository.cs | 2 +- .../Repositories/IImpactSnapshotRepository.cs | 2 +- .../Repositories/IJobHistoryRepository.cs | 4 +- .../Postgres}/Repositories/IJobRepository.cs | 4 +- .../Repositories/IMetricsRepository.cs | 4 +- .../Repositories/IPolicyRunJobRepository.cs | 2 +- .../Postgres}/Repositories/IRunRepository.cs | 2 +- .../Repositories/IScheduleRepository.cs | 2 +- .../Repositories/ITriggerRepository.cs | 4 +- .../Repositories/IWorkerRepository.cs | 4 +- .../Repositories/ImpactSnapshotRepository.cs | 2 +- .../Repositories/JobHistoryRepository.cs | 4 +- .../Postgres}/Repositories/JobRepository.cs | 4 +- .../Repositories/MetricsRepository.cs | 4 +- .../Repositories/PolicyRunJobRepository.cs | 2 +- .../Postgres}/Repositories/RunQueryOptions.cs | 2 +- .../Postgres}/Repositories/RunRepository.cs | 2 +- .../Repositories/RunSummaryService.cs | 2 +- .../Repositories/ScheduleQueryOptions.cs | 2 +- .../Repositories/ScheduleRepository.cs | 2 +- .../Repositories/TriggerRepository.cs | 4 +- .../Repositories/WorkerRepository.cs | 4 +- .../Postgres}/SchedulerDataSource.cs | 2 +- .../StellaOps.Scheduler.Persistence.csproj | 36 + .../StellaOps.Scheduler.Queue.csproj | 18 +- ...tellaOps.Scheduler.Storage.Postgres.csproj | 26 - .../Execution/PartitionMaintenanceWorker.cs | 2 +- .../Execution/RunnerExecutionService.cs | 2 +- .../Graph/GraphBuildBackgroundService.cs | 2 +- .../Graph/GraphBuildExecutionService.cs | 2 +- .../Graph/GraphOverlayBackgroundService.cs | 2 +- .../Graph/GraphOverlayExecutionService.cs | 2 +- .../Indexing/FailureSignatureIndexer.cs | 4 +- .../Planning/PlannerBackgroundService.cs | 2 +- .../Planning/PlannerExecutionService.cs | 2 +- .../PolicyRunDispatchBackgroundService.cs | 2 +- .../Policy/PolicyRunExecutionService.cs | 2 +- .../StellaOps.Scheduler.Worker.csproj | 8 +- .../StellaOps.Scheduler.Backfill.Tests.csproj | 12 +- .../FixtureImpactIndexTests.cs | 3 +- ...ellaOps.Scheduler.ImpactIndex.Tests.csproj | 14 +- .../PolicyRunModelsTests.cs | 11 + .../Properties/BackfillRangePropertyTests.cs | 5 +- .../Properties/CronNextRunPropertyTests.cs | 7 +- .../Properties/RetryBackoffPropertyTests.cs | 126 +- .../SamplePayloadTests.cs | 3 +- .../ScheduleSerializationTests.cs | 3 +- .../StellaOps.Scheduler.Models.Tests.csproj | 6 +- .../DistributedLockRepositoryTests.cs | 4 +- .../GraphJobRepositoryTests.cs | 6 +- .../JobIdempotencyTests.cs | 6 +- .../SchedulerMigrationTests.cs | 2 +- .../SchedulerPostgresFixture.cs | 10 +- .../SchedulerQueryDeterminismTests.cs | 8 +- ...ellaOps.Scheduler.Persistence.Tests.csproj | 26 + .../TriggerRepositoryTests.cs | 6 +- .../WorkerRepositoryTests.cs | 6 +- .../RedisSchedulerQueueTests.cs | 1 - ...erQueueServiceCollectionExtensionsTests.cs | 3 +- .../StellaOps.Scheduler.Queue.Tests.csproj | 20 +- ...ps.Scheduler.Storage.Postgres.Tests.csproj | 35 - .../CartographerWebhookClientTests.cs | 3 +- .../EventWebhookEndpointTests.cs | 3 +- .../FailureSignatureEndpointTests.cs | 7 +- .../GraphJobEndpointTests.cs | 3 +- .../GraphJobEventPublisherTests.cs | 3 +- .../PolicyRunEndpointTests.cs | 3 +- .../PolicySimulationEndpointTests.cs | 3 +- .../PolicySimulationMetricsProviderTests.cs | 5 +- .../RunEndpointTests.cs | 5 +- .../ScheduleEndpointTests.cs | 3 +- ...tellaOps.Scheduler.WebService.Tests.csproj | 16 +- .../GraphBuildExecutionServiceTests.cs | 37 +- .../GraphOverlayExecutionServiceTests.cs | 37 +- .../WorkerOTelCorrelationTests.cs | 17 +- .../PlannerBackgroundServiceTests.cs | 8 +- .../PlannerExecutionServiceTests.cs | 7 +- .../PlannerQueueDispatchServiceTests.cs | 19 + ...PolicyRunDispatchBackgroundServiceTests.cs | 5 +- .../PolicyRunExecutionServiceTests.cs | 5 +- .../PolicySimulationWebhookClientTests.cs | 5 +- .../Retry/WorkerRetryTests.cs | 4 +- .../RunnerExecutionServiceTests.cs | 7 +- .../StellaOps.Scheduler.Worker.Tests.csproj | 13 +- .../StellaOps.Signals.Scheduler.csproj | 4 +- ...aOps.Signals.Storage.Postgres.Tests.csproj | 35 - .../AGENTS.md | 24 - .../StellaOps.Signals.Storage.Postgres.csproj | 17 - src/Signals/StellaOps.Signals.sln | 479 +- src/Signals/StellaOps.Signals/Program.cs | 3 + .../Properties/launchSettings.json | 12 + .../Services/CallgraphIngestionService.cs | 10 +- .../Services/ReachabilityScoringService.cs | 2 +- .../StellaOps.Signals.csproj | 4 +- .../Probes/AirGapProbeLoader.cs | 439 + .../Probes/CoreProbeLoader.cs | 494 + .../Probes/IEbpfProbeLoader.cs | 106 + .../Schema/RuntimeCallEvent.cs | 231 + .../Services/IRuntimeSignalCollector.cs | 152 + .../Services/RuntimeSignalCollector.cs | 475 + .../StellaOps.Signals.Ebpf.csproj | 18 + .../EfCore/Context/SignalsDbContext.cs | 21 + .../SignalsPersistenceExtensions.cs | 87 + .../Migrations/001_initial_schema.sql | 507 + .../Migrations/_archived/pre_1.0/README.md | 23 + .../pre_1.0}/V0000_001__extensions.sql | 0 .../V1102_001__unknowns_scoring_schema.sql | 0 .../V1105_001__deploy_refs_graph_metrics.sql | 0 ...V3102_001__callgraph_relational_tables.sql | 0 .../Repositories/ICallGraphQueryRepository.cs | 2 +- .../PostgresCallGraphProjectionRepository.cs | 2 +- .../PostgresCallGraphQueryRepository.cs | 2 +- .../PostgresCallgraphRepository.cs | 2 +- .../PostgresDeploymentRefsRepository.cs | 2 +- .../PostgresGraphMetricsRepository.cs | 2 +- .../PostgresReachabilityFactRepository.cs | 2 +- .../PostgresReachabilityStoreRepository.cs | 2 +- .../PostgresUnknownsRepository.cs | 2 +- .../Postgres}/ServiceCollectionExtensions.cs | 4 +- .../Postgres}/SignalsDataSource.cs | 2 +- .../StellaOps.Signals.Persistence.csproj | 34 + .../EbpfSignalMergerTests.cs | 316 + .../RuntimeSignalCollectorTests.cs | 209 + .../StellaOps.Signals.Ebpf.Tests.csproj} | 22 +- .../CallGraphProjectionIntegrationTests.cs | 7 +- .../CallGraphSyncServiceTests.cs | 7 +- .../PostgresCallgraphRepositoryTests.cs | 7 +- .../SignalsPostgresFixture.cs | 3 +- ...StellaOps.Signals.Persistence.Tests.csproj | 24 + .../CallgraphIngestionServiceTests.cs | 3 +- .../EdgeBundleIngestionServiceTests.cs | 3 +- .../EvidenceWeightPolicyTests.cs | 6 +- .../EvidenceWeightedScoreCalculatorTests.cs | 10 +- .../GroundTruth/GroundTruthValidatorTests.cs | 4 +- .../ReachabilityLatticeTests.cs | 2 +- .../ReachabilityUnionIngestionServiceTests.cs | 3 +- .../RouterEventsPublisherTests.cs | 3 +- .../RuntimeFactsBatchIngestionTests.cs | 3 +- .../SimpleJsonCallgraphParserGateTests.cs | 3 +- .../StellaOps.Signals.Tests.csproj | 23 +- .../UnknownsDecayServiceTests.cs | 3 +- src/Signer/StellaOps.Signer.sln | 575 +- .../StellaOps.Signer.Core/PredicateTypes.cs | 60 +- .../Predicates/DeltaPredicateSchemas.cs | 462 + .../StellaOps.Signer.Infrastructure.csproj | 10 +- .../Auth/SignerAuthTests.cs | 1 - .../Availability/PluginAvailabilityTests.cs | 1 - .../Contract/SignerContractSnapshotTests.cs | 1 - .../MultiPluginSignVerifyIntegrationTests.cs | 3 +- .../TamperedPayloadVerificationTests.cs | 1 - .../Keyless/KeylessDsseSignerTests.cs | 2 +- .../Keyless/KeylessSigningIntegrationTests.cs | 2 +- .../Negative/SignerNegativeTests.cs | 1 - .../Observability/SignerOTelTraceTests.cs | 1 - .../StellaOps.Signer.Tests.csproj | 25 +- .../StellaOps.Signer.WebService/Program.cs | 1 + .../Properties/launchSettings.json | 12 + .../StellaOps.Signer.WebService.csproj | 20 +- .../StellaOps.Signer/StellaOps.Signer.sln | 174 - .../Migrations/001_initial_schema.sql | 105 + .../20251214000001_AddKeyManagementSchema.sql | 0 .../Migrations/_archived/pre_1.0/README.md | 21 + .../StellaOps.Signer.KeyManagement.csproj | 4 +- .../ICertificateChainValidator.cs | 6 +- .../StellaOps.Signer.Keyless.csproj | 10 +- .../StellaOps.SmRemote.Service/Program.cs | 1 + .../Properties/launchSettings.json | 12 + src/SmRemote/StellaOps.SmRemote.sln | 164 + src/StellaOps.AdvisoryAI.sln | 58 - src/StellaOps.AirGap.sln | 128 - src/StellaOps.Aoc.sln | 63 - src/StellaOps.Attestor.sln | 212 - src/StellaOps.Authority.sln | 184 - src/StellaOps.Bench.sln | 96 - src/StellaOps.BinaryIndex.sln | 103 - src/StellaOps.Cartographer.sln | 33 - src/StellaOps.Cli.sln | 63 - src/StellaOps.Concelier.sln | 725 - .../StellaOps.Events.Provenance.Tests.csproj | 21 - src/StellaOps.EvidenceLocker.sln | 65 - src/StellaOps.Excititor.sln | 291 - src/StellaOps.ExportCenter.sln | 79 - src/StellaOps.Gateway.sln | 40 - src/StellaOps.Graph.sln | 68 - src/StellaOps.Infrastructure.sln | 925 - src/StellaOps.IssuerDirectory.sln | 79 - src/StellaOps.Notify.sln | 186 - src/StellaOps.Orchestrator.sln | 67 - src/StellaOps.Policy.sln | 182 - src/StellaOps.Replay.sln | 77 - src/StellaOps.SbomService.sln | 47 - src/StellaOps.Scanner.sln | 927 - src/StellaOps.Scheduler.sln | 130 - src/StellaOps.Signals.sln | 77 - src/StellaOps.Signer.sln | 65 - src/StellaOps.TaskRunner.sln | 79 - src/StellaOps.Telemetry.sln | 49 - src/StellaOps.Tests.sln | 14 - src/StellaOps.Tests.slnx | 2 - src/StellaOps.VexHub.sln | 63 - src/StellaOps.VexLens.sln | 40 - src/StellaOps.VulnExplorer.sln | 33 - src/StellaOps.Zastava.sln | 70 - src/StellaOps.sln | 12989 ++++++----- .../StellaOps.Symbols.Bundle.csproj | 4 +- .../StellaOps.Symbols.Client.csproj | 8 +- .../StellaOps.Symbols.Core.csproj | 4 +- .../StellaOps.Symbols.Infrastructure.csproj | 6 +- .../StellaOps.Symbols.Server/Program.cs | 3 - .../Properties/launchSettings.json | 12 + .../StellaOps.Symbols.Server.csproj | 2 +- ...s.TaskRunner.Storage.Postgres.Tests.csproj | 34 - ...ellaOps.TaskRunner.Storage.Postgres.csproj | 12 - src/TaskRunner/StellaOps.TaskRunner.sln | 340 +- .../StellaOps.TaskRunner.Client.csproj | 4 +- .../Execution/TaskRunnerTelemetry.cs | 8 +- .../StellaOps.TaskRunner.Core.csproj | 4 +- ...StellaOps.TaskRunner.Infrastructure.csproj | 4 +- .../ApiDeprecationTests.cs | 4 +- .../BundleImportEvidenceTests.cs | 20 +- .../BundleIngestionStepExecutorTests.cs | 11 +- .../FilesystemPackRunArtifactReaderTests.cs | 7 +- .../FilesystemPackRunArtifactUploaderTests.cs | 12 +- .../FilesystemPackRunDispatcherTests.cs | 2 +- .../PackRunAttestationTests.cs | 34 +- .../PackRunEvidenceSnapshotTests.cs | 40 +- .../PackRunIncidentModeTests.cs | 34 +- .../PackRunProvenanceWriterTests.cs | 5 +- .../PackRunTimelineEventTests.cs | 30 +- .../SealedInstallEnforcerTests.cs | 18 +- .../StellaOps.TaskRunner.Tests.csproj | 13 +- .../TaskRunnerClientTests.cs | 21 +- .../StellaOps.TaskRunner.WebService.csproj | 8 +- .../StellaOps.TaskRunner.Worker/Program.cs | 2 + .../Services/PackRunWorkerService.cs | 7 +- .../StellaOps.TaskRunner.Worker.csproj | 8 +- .../StellaOps.TaskRunner.sln | 104 - .../EfCore/Context/TaskRunnerDbContext.cs | 21 + .../TaskRunnerPersistenceExtensions.cs} | 17 +- .../PostgresPackRunApprovalStore.cs | 2 +- .../PostgresPackRunEvidenceStore.cs | 2 +- .../Repositories/PostgresPackRunLogStore.cs | 2 +- .../Repositories/PostgresPackRunStateStore.cs | 2 +- .../Postgres}/TaskRunnerDataSource.cs | 2 +- .../StellaOps.TaskRunner.Persistence.csproj | 30 + .../PostgresPackRunStateStoreTests.cs | 8 +- ...llaOps.TaskRunner.Persistence.Tests.csproj | 23 + .../TaskRunnerPostgresFixture.cs | 4 +- .../MetricLabelAnalyzerTests.cs | 2 +- ...StellaOps.Telemetry.Analyzers.Tests.csproj | 16 +- .../TestCategories.cs | 11 + .../StellaOps.Telemetry.Analyzers.csproj | 10 +- .../AsyncResumeTestHarness.cs | 4 +- .../CliTelemetryContextTests.cs | 2 +- .../Directory.Build.props | 8 - .../Directory.Build.targets | 27 - .../GoldenSignalMetricsTests.cs | 3 +- .../IncidentModeServiceTests.cs | 3 +- .../MetricLabelGuardTests.cs | 2 +- .../ProofCoverageMetricsTests.cs | 3 +- .../SealedModeFileExporterTests.cs | 3 +- .../SealedModeTelemetryServiceTests.cs | 3 +- .../StellaOps.Telemetry.Core.Tests.csproj | 18 +- .../TelemetryContextAccessorTests.cs | 2 +- .../TelemetryContextTests.cs | 3 +- .../TelemetryExporterGuardTests.cs | 5 +- .../TelemetryPropagationHandlerTests.cs | 2 +- .../TelemetryPropagationMiddlewareTests.cs | 2 +- .../TimeToFirstSignalMetricsTests.cs | 2 +- .../TtfsIngestionServiceTests.cs | 3 +- .../RedactingLogProcessor.cs | 2 + .../StellaOps.Telemetry.Core.csproj | 12 +- .../TelemetryContextAccessor.cs | 6 +- src/Telemetry/StellaOps.Telemetry.sln | 102 + .../StellaOps.TimelineIndexer.sln | 424 +- ...aOps.TimelineIndexer.Infrastructure.csproj | 10 +- .../EvidenceLinkageIntegrationTests.cs | 9 +- .../StellaOps.TimelineIndexer.Tests.csproj | 29 +- .../TimelineIngestionServiceTests.cs | 8 +- .../TimelineIngestionWorkerTests.cs | 3 +- .../TimelineIntegrationTests.cs | 10 +- .../TimelineQueryServiceTests.cs | 8 +- .../TimelineWorkerEndToEndTests.cs | 7 +- ...tellaOps.TimelineIndexer.WebService.csproj | 4 +- .../StellaOps.TimelineIndexer.Worker.csproj | 2 +- .../StellaOps.TimelineIndexer.sln | 90 - .../FixtureUpdater/FixtureUpdater.csproj | 10 +- src/Tools/FixtureUpdater/Program.cs | 5 +- .../LanguageAnalyzerSmoke.csproj | 4 +- .../NotifySmokeCheck/NotifySmokeCheck.csproj | 2 +- .../PolicyDslValidator.csproj | 4 +- .../PolicySchemaExporter.csproj | 11 +- src/Tools/PolicySchemaExporter/Program.cs | 9 +- .../PolicySimulationSmoke.csproj | 6 +- .../RustFsMigrator/RustFsMigrator.csproj | 2 +- src/Tools/StellaOps.Tools.sln | 823 + .../StellaOps.Unknowns.Core.csproj | 2 +- .../Context/UnknownsDbContext.cs | 38 + .../UnknownsPersistenceExtensions.cs | 75 + .../README.md | 54 + .../Repositories/UnknownEfRepository.cs | 291 + ...ellaOps.Unknowns.Persistence.EfCore.csproj | 26 + .../EfCore/CompiledModels/.gitkeep | 2 + .../EfCore/Context/UnknownsDbContext.cs | 38 + .../EfCore/Entities/.gitkeep | 2 + .../EfCore/Mappings/.gitkeep | 1 + .../Repositories/UnknownEfRepository.cs | 234 + .../UnknownsPersistenceExtensions.cs | 136 + .../InMemory/Repositories/.gitkeep | 1 + .../Migrations/001_initial_schema.sql | 523 + .../_archived/pre_1.0}/001_initial_schema.sql | 0 .../pre_1.0}/002_scoring_extension.sql | 0 .../Postgres}/PostgresUnknownPersister.cs | 2 +- .../Repositories/PostgresUnknownRepository.cs | 14 +- .../StellaOps.Unknowns.Persistence.csproj | 33 + ...StellaOps.Unknowns.Storage.Postgres.csproj | 27 - .../Services/UnknownRankerTests.cs | 10 +- .../StellaOps.Unknowns.Core.Tests.csproj | 20 +- .../PostgresUnknownRepositoryTests.cs | 32 +- ...tellaOps.Unknowns.Persistence.Tests.csproj | 24 + ...Ops.Unknowns.Storage.Postgres.Tests.csproj | 36 - .../StellaOps.VexHub.WebService/Program.cs | 4 +- .../Properties/launchSettings.json | 12 + .../StellaOps.VexHub.WebService.csproj | 22 +- src/VexHub/StellaOps.VexHub.sln | 635 +- .../StellaOps.VexHub.Core.csproj | 8 +- .../EfCore/Context/VexHubDbContext.cs | 21 + .../Extensions/VexHubPersistenceExtensions.cs | 46 + .../Migrations/001_initial_schema.sql | 0 .../Postgres}/Models/VexConflictEntity.cs | 2 +- .../Postgres}/Models/VexIngestionJobEntity.cs | 2 +- .../Postgres}/Models/VexProvenanceEntity.cs | 2 +- .../Postgres}/Models/VexSourceEntity.cs | 2 +- .../Postgres}/Models/VexStatementEntity.cs | 2 +- .../PostgresVexProvenanceRepository.cs | 4 +- .../PostgresVexStatementRepository.cs | 4 +- .../Postgres}/VexHubDataSource.cs | 2 +- .../StellaOps.VexHub.Persistence.csproj} | 18 +- ...xHubPostgresServiceCollectionExtensions.cs | 29 - .../StellaOps.VexHub.Core.Tests.csproj | 2 +- ...laOps.VexHub.Storage.Postgres.Tests.csproj | 16 - .../StellaOps.VexHub.WebService.Tests.csproj | 7 +- .../Migrations/001_consensus_projections.sql | 152 + .../PostgresConsensusProjectionStore.cs | 582 + .../StellaOps.VexLens.Persistence.csproj | 26 + src/VexLens/StellaOps.VexLens.sln | 291 + .../VexLensServiceCollectionExtensions.cs | 112 +- .../Options/VexLensOptions.cs | 23 +- .../Services/VexDeltaComputeService.cs | 318 + .../StellaOps.VexLens.Core.csproj | 4 +- .../StellaOps.VexLens.csproj | 21 +- .../DualWriteConsensusProjectionStore.cs | 306 + .../InMemoryConsensusProjectionStore.cs | 30 +- .../PostgresConsensusProjectionStoreProxy.cs | 552 + .../StellaOps.VexLens.Core.Tests.csproj | 21 +- .../StellaOps.VulnExplorer.Api/Program.cs | 32 +- .../Properties/launchSettings.json | 12 + .../StellaOps.VulnExplorer.Api.csproj | 4 +- .../Contracts/EvidenceSubgraphContracts.cs | 514 + src/VulnExplorer/StellaOps.VulnExplorer.sln | 29 + .../ACCESSIBILITY_AUDIT_BINARY_RESOLUTION.md | 259 + .../e2e/binary-resolution.e2e.spec.ts | 318 + .../StellaOps.Web/src/app/app.component.html | 113 +- .../StellaOps.Web/src/app/app.component.scss | 190 +- .../StellaOps.Web/src/app/app.component.ts | 91 +- src/Web/StellaOps.Web/src/app/app.routes.ts | 40 +- .../app/core/api/binary-resolution.client.ts | 142 + .../app/core/api/binary-resolution.models.ts | 268 + .../src/app/core/api/reachgraph.models.ts | 123 + .../src/app/core/auth/auth.service.ts | 3 + .../src/app/core/models/proof-spine.model.ts | 215 + .../src/app/core/navigation/index.ts | 3 + .../app/core/navigation/navigation.config.ts | 270 + .../app/core/navigation/navigation.service.ts | 267 + .../app/core/navigation/navigation.types.ts | 97 + .../app/core/services/audit-pack.service.ts | 190 + .../app/core/services/theme.service.spec.ts | 228 + .../src/app/core/services/theme.service.ts | 267 + .../src/app/core/services/toast.service.ts | 94 + .../services/view-preference.service.spec.ts | 183 + .../core/services/view-preference.service.ts | 141 + .../__tests__/findings-navigation.e2e.spec.ts | 194 + .../findings-container.component.spec.ts | 181 + .../container/findings-container.component.ts | 232 + .../findings/findings-list.component.html | 44 +- .../findings/findings-list.component.scss | 202 +- .../findings/findings-list.component.ts | 50 +- .../src/app/features/findings/index.ts | 3 + .../home/home-dashboard.component.scss | 603 + .../features/home/home-dashboard.component.ts | 368 + .../features/home/home-dashboard.service.ts | 230 + .../attestation-links.component.ts | 241 + .../compare-panel/compare-panel.component.ts | 491 + .../export-dialog/export-dialog.component.ts | 543 + .../keyboard-shortcuts-help.component.ts | 221 + .../lineage-compare-panel.component.ts | 752 + .../lineage-compare.component.ts | 431 + .../lineage-component-diff.component.ts | 350 + .../lineage-controls.component.ts | 248 + .../lineage-detail-panel.component.ts | 561 + .../lineage-edge/lineage-edge.component.ts | 241 + .../lineage-export-buttons.component.ts | 521 + .../lineage-export-dialog.component.ts | 740 + .../lineage-graph-container.component.ts | 437 + .../lineage-graph/lineage-graph.component.ts | 615 + .../lineage-hover-card.component.ts | 295 + .../lineage-minimap.component.ts | 231 + .../lineage-mobile-compare.component.ts | 1077 + .../lineage-node/lineage-node.component.ts | 311 + .../lineage-provenance-chips.component.ts | 361 + .../lineage-provenance-compare.component.ts | 744 + .../lineage-sbom-diff.component.ts | 741 + .../lineage-timeline-slider.component.ts | 761 + .../lineage-vex-delta.component.ts | 352 + .../lineage-vex-diff.component.ts | 670 + .../lineage-why-safe-panel.component.ts | 925 + .../reachability-diff-view.component.ts | 321 + .../replay-hash-display.component.ts | 241 + .../timeline-slider.component.ts | 516 + .../vex-diff-view/vex-diff-view.component.ts | 260 + .../why-safe-panel.component.ts | 715 + .../lineage-accessibility.directive.ts | 238 + .../lineage-graph-highlight.directive.ts | 391 + .../lineage-keyboard-shortcuts.directive.ts | 124 + .../directives/lineage-keyboard.directive.ts | 371 + .../src/app/features/lineage/index.ts | 47 + .../app/features/lineage/lineage.routes.ts | 35 + .../features/lineage/models/lineage.models.ts | 388 + .../routing/lineage-compare-routing.guard.ts | 391 + .../services/lineage-export.service.ts | 679 + .../lineage/services/lineage-graph.service.ts | 381 + .../lineage/styles/lineage-accessibility.scss | 179 + .../lineage/styles/lineage-mobile.styles.ts | 240 + .../reachability-center.component.ts | 56 +- .../triage-list/triage-list.component.ts | 11 +- .../features/triage/models/gating.model.ts | 26 +- .../evidence-graph.component.html | 176 + .../evidence-graph.component.scss | 251 + .../evidence-graph.component.ts | 456 + .../policy-breadcrumb.component.html | 129 + .../policy-breadcrumb.component.scss | 295 + .../policy-breadcrumb.component.ts | 163 + .../verdict-actions.component.html | 111 + .../verdict-actions.component.scss | 152 + .../verdict-actions.component.ts | 224 + .../verdict-detail-panel.component.html | 391 + .../verdict-detail-panel.component.scss | 455 + .../verdict-detail-panel.component.ts | 206 + .../src/app/features/verdicts/index.ts | 20 + .../src/app/features/verdicts/models/index.ts | 1 + .../verdicts/models/verdict.models.ts | 371 + .../app/features/verdicts/services/index.ts | 1 + .../verdicts/services/verdict.service.ts | 368 + .../citation-link/citation-link.component.ts | 587 + .../evidence-subgraph.component.spec.ts | 814 + .../components/evidence-subgraph.stories.ts | 686 + .../evidence-subgraph.component.ts | 1142 + .../evidence-tree/evidence-tree.component.ts | 602 + .../triage-card/triage-card.component.ts | 679 + .../triage-filters.component.ts | 708 + .../verdict-explanation.component.ts | 632 + .../models/evidence-subgraph.models.ts | 191 + .../services/evidence-subgraph.service.ts | 146 + .../vulnerability-detail.component.html | 32 + .../vulnerability-detail.component.ts | 67 +- .../vulnerability-explorer.component.html | 717 +- .../vulnerability-explorer.component.scss | 485 +- .../vulnerability-explorer.component.ts | 124 +- .../accordion/accordion.component.ts | 514 + .../components/alert/alert.component.ts | 449 + .../attestation-viewer.component.ts | 584 + ...export-audit-pack-button.component.spec.ts | 229 + .../export-audit-pack-button.component.ts | 124 + ...export-audit-pack-dialog.component.spec.ts | 169 + .../export-audit-pack-dialog.component.ts | 166 + .../app/shared/components/audit-pack/index.ts | 8 + .../components/avatar/avatar.component.ts | 311 + .../components/badge/badge.component.ts | 242 + .../breadcrumb/breadcrumb.component.ts | 206 + .../components/button/button.component.ts | 362 + .../shared/components/card/card.component.ts | 326 + .../command-palette.component.scss | 280 + .../command-palette.component.ts | 382 + .../components/confidence-badge.component.ts | 26 +- .../confirm-dialog.component.ts | 291 + .../copy-attestation-button.component.spec.ts | 128 + .../copy-attestation-button.component.ts | 153 + .../copy-button/copy-button.component.ts | 211 + .../data-table/data-table.component.ts | 502 + .../components/divider/divider.component.ts | 195 + .../components/dropdown/dropdown.component.ts | 952 + .../empty-state/empty-state.component.ts | 172 + .../evidence-drawer.component.spec.ts | 356 + .../evidence-drawer.component.ts | 649 + .../components/evidence-drawer/index.ts | 1 + .../components/finding-detail.component.ts | 119 +- .../findings-view-toggle.component.spec.ts | 73 + .../findings-view-toggle.component.ts | 93 + .../function-diff/function-diff.component.ts | 593 + .../src/app/shared/components/index.ts | 19 + .../keyboard-shortcuts.component.ts | 324 + .../components/loading/loading.component.ts | 223 + .../components/modal/modal.component.ts | 453 + .../navigation-menu.component.scss | 398 + .../navigation-menu.component.ts | 217 + .../pagination/pagination.component.ts | 352 + .../progress-bar/progress-bar.component.ts | 157 + .../__tests__/proof-spine.e2e.spec.ts | 299 + .../chain-integrity-badge.component.ts | 74 + .../shared/components/proof-spine/index.ts | 11 + .../proof-spine/proof-badges-row.component.ts | 159 + .../proof-spine/proof-segment.component.ts | 242 + .../proof-spine/proof-spine.component.spec.ts | 114 + .../proof-spine/proof-spine.component.ts | 209 + .../segment-detail-modal.component.ts | 503 + .../resolution-chip.component.spec.ts | 229 + .../resolution-chip.component.ts | 322 + .../score/score-badge.component.scss | 8 +- .../search-input/search-input.component.ts | 473 + .../components/skeleton/skeleton.component.ts | 257 + .../smart-diff-badge.component.spec.ts | 133 + .../smart-diff-badge.component.ts | 234 + .../stat-card/stat-card.component.ts | 465 + .../shared/components/tabs/tabs.component.ts | 406 + .../theme-toggle/theme-toggle.component.ts | 359 + .../toast/toast-container.component.ts | 246 + .../components/tooltip/tooltip.component.ts | 169 + .../src/app/shared/components/ui/index.ts | 70 + .../user-menu/user-menu.component.scss | 240 + .../user-menu/user-menu.component.ts | 207 + .../shared/components/vex-trust-chip/index.ts | 1 + .../vex-trust-chip.component.spec.ts | 326 + .../vex-trust-chip.component.ts | 410 + .../components/vex-trust-popover/index.ts | 1 + .../vex-trust-popover.component.spec.ts | 335 + .../vex-trust-popover.component.ts | 626 + .../src/app/shared/pipes/format.pipes.ts | 243 + .../src/app/shared/pipes/truncate.pipe.ts | 28 + .../stories/trust/vex-trust-chip.stories.ts | 404 + src/Web/StellaOps.Web/src/styles.scss | 28 +- src/Web/StellaOps.Web/src/styles/_forms.scss | 498 + .../src/styles/_interactions.scss | 450 + src/Web/StellaOps.Web/src/styles/_mixins.scss | 91 +- .../src/styles/tokens/_colors.scss | 435 + .../src/styles/tokens/_motion.scss | 35 + .../Configuration/ZastavaAgentOptions.cs | 2 +- .../StellaOps.Zastava.Agent.csproj | 10 +- .../ReachabilityRuntimeOptions.cs | 2 +- .../Configuration/ZastavaObserverOptions.cs | 4 +- .../Probes/EbpfProbeManager.cs | 459 + .../StellaOps.Zastava.Observer.csproj | 13 +- .../Properties/launchSettings.json | 12 + .../StellaOps.Zastava.Webhook.csproj | 6 +- src/Zastava/StellaOps.Zastava.sln | 598 +- .../StellaOps.Zastava.Core.csproj | 12 +- .../StellaOps.Zastava.Core.Tests.csproj | 12 +- .../Windows/WindowsContainerRuntimeTests.cs | 46 +- .../ContainerRuntimePollerTests.cs | 5 + .../Posture/RuntimePostureEvaluatorTests.cs | 16 +- .../StellaOps.Zastava.Observer.Tests.csproj | 10 + .../StellaOps.Zastava.Webhook.Tests.csproj | 9 +- .../CanonicalizationBoundaryAnalyzerTests.cs | 3 +- ...ellaOps.Determinism.Analyzers.Tests.csproj | 18 +- .../AnalyzerReleases.Unshipped.md | 2 - .../StellaOps.Determinism.Analyzers.csproj | 4 +- .../StellaOps.Audit.ReplayToken.csproj | 2 +- .../Models/AuditBundleManifest.cs | 10 + .../Services/AuditPackExportService.cs | 420 + .../Services/ReplayAttestationService.cs | 420 + .../Services/ReplayTelemetry.cs | 399 + .../Services/VerdictReplayPredicate.cs | 502 + .../StellaOps.AuditPack.csproj | 4 + .../StellaOps.Auth.Security.csproj | 12 +- .../StellaOps.Canonical.Json.Tests.csproj | 14 +- .../StellaOps.Canonical.Json.csproj | 2 +- .../StellaOps.Canonicalization.csproj | 5 +- .../StellaOps.Configuration.csproj | 14 +- ...ps.Cryptography.DependencyInjection.csproj | 12 +- .../AwsKmsFacade.cs | 5 - .../StellaOps.Cryptography.Kms.csproj | 12 +- ...ps.Cryptography.Plugin.BouncyCastle.csproj | 4 +- .../CryptoProGostCryptoProvider.cs | 3 + ...laOps.Cryptography.Plugin.CryptoPro.csproj | 8 +- .../GostCryptography.Tests.csproj | 11 +- .../GostCryptography/GostCryptography.csproj | 8 +- ...Ops.Cryptography.Plugin.EIDAS.Tests.csproj | 25 +- ...StellaOps.Cryptography.Plugin.EIDAS.csproj | 7 +- ...tography.Plugin.OfflineVerification.csproj | 2 +- ...Ops.Cryptography.Plugin.OpenSslGost.csproj | 6 +- ...aOps.Cryptography.Plugin.Pkcs11Gost.csproj | 12 +- .../PqSoftCryptoProvider.cs | 63 +- ...tellaOps.Cryptography.Plugin.PqSoft.csproj | 6 +- .../SmRemoteHttpProviderTests.cs | 3 +- ....Cryptography.Plugin.SmRemote.Tests.csproj | 4 +- ...ps.Cryptography.Plugin.SmSoft.Tests.csproj | 21 +- ...tellaOps.Cryptography.Plugin.SmSoft.csproj | 8 +- ...ellaOps.Cryptography.Plugin.WineCsp.csproj | 8 +- ...Ops.Cryptography.PluginLoader.Tests.csproj | 13 +- ...StellaOps.Cryptography.PluginLoader.csproj | 6 +- .../OfflineVerificationCryptoProvider.cs | 38 + ...raphy.Providers.OfflineVerification.csproj | 2 +- .../PolicyProvidersTests.cs | 1 - .../PqSoftCryptoProviderTests.cs | 3 + .../SimRemoteProviderTests.cs | 3 +- .../StellaOps.Cryptography.Tests.csproj | 20 +- .../StellaOps.Cryptography/EcdsaSigner.cs | 2 +- .../SignatureAlgorithms.cs | 6 + .../StellaOps.Cryptography.csproj | 10 +- .../StellaOps.DeltaVerdict.csproj | 4 - .../StellaOps.DependencyInjection.csproj | 16 +- .../StellaOps.Evidence.Bundle.csproj | 2 +- .../EvidenceRecordTests.cs | 36 +- .../StellaOps.Evidence.Core.Tests.csproj | 15 +- .../EfCore/Context/EvidenceDbContext.cs | 21 + .../EvidencePersistenceExtensions.cs | 42 + .../Migrations/001_initial_schema.sql | 0 .../Postgres}/EvidenceDataSource.cs | 2 +- .../Postgres}/PostgresEvidenceStore.cs | 2 +- .../Postgres}/PostgresEvidenceStoreFactory.cs | 2 +- .../StellaOps.Evidence.Persistence.csproj | 30 + .../ServiceCollectionExtensions.cs | 55 - ...StellaOps.Evidence.Storage.Postgres.csproj | 22 - .../StellaOps.Evidence.csproj | 7 +- .../Context/StellaOpsDbContextBase.cs | 64 + .../Extensions/DbContextServiceExtensions.cs | 155 + .../TenantConnectionInterceptor.cs | 120 + .../StellaOps.Infrastructure.EfCore.csproj | 24 + .../AsyncLocalTenantContextAccessor.cs | 56 + .../Tenancy/ITenantContextAccessor.cs | 14 + .../Tenancy/SystemTenantContextAccessor.cs | 16 + .../Migrations/MigrationDependency.cs | 274 + .../Migrations/MigrationTelemetry.cs | 218 + .../Migrations/MigrationValidator.cs | 241 + .../StellaOps.Infrastructure.Postgres.csproj | 16 +- .../StellaOps.Interop.csproj | 2 +- .../StellaOps.Interop/ToolManager.cs | 3 +- .../StellaOps.IssuerDirectory.Client.csproj | 6 +- .../Plugins/IMessagingTransportPlugin.cs | 23 - .../Plugins/MessagingPluginLoader.cs | 113 - .../MessagingTransportRegistrationContext.cs | 52 - .../StellaOps.Metrics.csproj | 2 +- .../Manifest/PluginManifest.cs | 233 + .../Manifest/PluginManifestLoader.cs | 524 + .../Manifest/PluginRegistry.cs | 287 + .../StellaOps.Plugin/StellaOps.Plugin.csproj | 7 +- .../ProvcacheEndpointExtensions.cs | 2 + .../StellaOps.Provcache.Api.csproj | 4 +- .../StellaOps.Provcache.Postgres.csproj | 10 +- .../StellaOps.Provcache.Valkey.csproj | 10 +- .../StellaOps.Provcache.csproj | 16 +- .../StellaOps.Provenance/DocumentStubs.cs | 2 + .../IReachGraphCache.cs | 59 + .../ReachGraphCacheOptions.cs | 40 + .../ReachGraphValkeyCache.cs | 261 + .../StellaOps.ReachGraph.Cache.csproj | 28 + .../IReachGraphRepository.cs | 135 + .../Migrations/001_reachgraph_store.sql | 141 + .../PostgresReachGraphRepository.cs | 345 + .../StellaOps.ReachGraph.Persistence.csproj | 32 + .../StellaOps.ReachGraph/AGENTS.md | 369 + .../Hashing/ReachGraphDigestComputer.cs | 113 + .../Schema/EdgeExplanation.cs | 77 + .../Schema/ReachGraphEdge.cs | 24 + .../Schema/ReachGraphMinimal.cs | 76 + .../Schema/ReachGraphNode.cs | 81 + .../Schema/ReachGraphProvenance.cs | 71 + .../CanonicalReachGraphSerializer.cs | 462 + .../Signing/IReachGraphKeyStore.cs | 45 + .../Signing/IReachGraphSignerService.cs | 98 + .../Signing/ReachGraphSignerService.cs | 223 + .../StellaOps.ReachGraph.csproj | 24 + .../Export/ReplayManifestExporterTests.cs | 367 + .../StellaOps.Replay.Core.Tests.csproj | 18 +- .../Export/IReplayManifestExporter.cs | 245 + .../Export/ReplayExportModels.cs | 395 + .../Export/ReplayManifestExporter.cs | 355 + .../FeedSnapshotCoordinatorService.cs | 2 +- .../Schemas/replay-export.schema.json | 447 + .../StellaOps.Replay.Core.csproj | 4 +- .../StellaOps.Replay/Engine/ReplayEngine.cs | 3 +- .../StellaOps.Replay/StellaOps.Replay.csproj | 7 +- .../StellaOps.Resolver.Tests.csproj | 15 +- .../StellaOps.Resolver.csproj | 3 +- .../StellaOps.Signals.Contracts.csproj | 2 +- .../Connectors/ConnectorSecurityTestBase.cs | 4 +- .../Fixtures/ValkeyFixture.cs | 3 +- .../StellaOps.TestKit.csproj | 24 +- .../StellaOps.TestKit/TestCategories.cs | 24 + src/__Libraries/StellaOps.Verdict/AGENTS.md | 144 + .../StellaOps.Verdict/Api/VerdictContracts.cs | 159 + .../StellaOps.Verdict/Api/VerdictEndpoints.cs | 351 + .../Contexts/verdict-1.0.jsonld | 471 + .../Export/VerdictBundleExporter.cs | 445 + .../Oci/OciAttestationPublisher.cs | 825 + .../Persistence/IVerdictStore.cs | 147 + .../Migrations/001_create_verdicts.sql | 107 + .../Persistence/PostgresVerdictStore.cs | 301 + .../Persistence/VerdictRow.cs | 84 + .../StellaOps.Verdict/Schema/StellaVerdict.cs | 635 + .../Services/VerdictAssemblyService.cs | 394 + .../Services/VerdictSigningService.cs | 302 + .../StellaOps.Verdict.csproj | 28 + .../AirGapTrustStoreIntegrationTests.cs | 3 +- .../AuditPackExportServiceIntegrationTests.cs | 411 + .../AuditReplayE2ETests.cs | 3 +- .../StellaOps.AuditPack.Tests.csproj | 15 +- .../Properties/CanonicalJsonProperties.cs | 1 + .../StellaOps.Canonicalization.Tests.csproj | 13 +- .../CloudKmsClientTests.cs | 8 +- .../FileKmsClientTests.cs | 3 +- .../StellaOps.Cryptography.Kms.Tests.csproj | 18 +- .../OfflineVerificationProviderTests.cs | 4 +- ...hy.Plugin.OfflineVerification.Tests.csproj | 8 +- .../BouncyCastleCapabilityDetectionTests.cs | 1 - .../BouncyCastleEd25519CryptoProviderTests.cs | 3 +- .../BouncyCastleErrorClassificationTests.cs | 1 - .../BouncyCastleSignVerifyRoundtripTests.cs | 1 - .../CryptoProCapabilityDetectionTests.cs | 3 +- .../CryptoProGostSignerTests.cs | 3 +- .../DefaultCryptoHashTests.cs | 3 +- .../DefaultCryptoHmacTests.cs | 3 +- .../DefaultCryptoProviderSigningTests.cs | 3 +- .../EidasCapabilityDetectionTests.cs | 1 - .../KmsHsmConnectorTests.cs | 44 +- .../LibsodiumCryptoProviderTests.cs | 3 +- .../Pkcs11GostProviderTests.cs | 3 +- .../SimRemoteCapabilityDetectionTests.cs | 6 +- .../StellaOps.Cryptography.Tests.csproj | 16 +- .../StellaOps.DeltaVerdict.Tests.csproj | 9 +- .../CrossModuleEvidenceLinkingTests.cs | 5 +- .../EvidencePostgresContainerFixture.cs | 71 + .../PostgresEvidenceStoreIntegrationTests.cs | 5 +- ...tellaOps.Evidence.Persistence.Tests.csproj | 29 + .../EvidencePostgresContainerFixture.cs | 185 - ...Ops.Evidence.Storage.Postgres.Tests.csproj | 22 - .../Budgets/EvidenceBudgetServiceTests.cs | 2 +- .../StellaOps.Evidence.Tests.csproj | 12 +- .../Migrations/StartupMigrationHostTests.cs | 2 +- .../PostgresFixtureTests.cs | 1 - ...laOps.Infrastructure.Postgres.Tests.csproj | 21 +- .../StellaOps.Metrics.Tests.csproj | 12 +- .../MinimalApiBindingIntegrationTests.cs | 1 - ...laOps.Microservice.AspNetCore.Tests.csproj | 8 +- .../StellaRouterBridgeIntegrationTests.cs | 1 - ...llaOps.Microservice.SourceGen.Tests.csproj | 39 - .../PluginCompatibilityCheckerTests.cs | 183 + .../PluginHostOptionsTests.cs | 229 + .../StellaOps.Plugin.Tests/PluginHostTests.cs | 235 + .../StellaOps.Plugin.Tests.csproj | 22 +- .../EvidenceChunkerTests.cs | 3 +- .../MinimalProofExporterTests.cs | 3 +- .../ProvcacheOciAttestationBuilderTests.cs | 13 +- .../StellaOps.Provcache.Tests.csproj | 19 +- .../StorageIntegrationTests.cs | 3 +- .../ProvenanceExtensionsTests.cs | 6 +- .../StellaOps.Provenance.Tests.csproj | 14 + .../CanonicalSerializerTests.cs | 293 + .../DigestComputerTests.cs | 214 + .../EdgeExplanationTests.cs | 174 + .../feature-flag-guards.reachgraph.min.json | 1 + .../simple-single-path.reachgraph.min.json | 1 + .../GoldenSampleTests.cs | 178 + .../StellaOps.ReachGraph.Tests.csproj | 31 + .../ReachabilityReplayWriterTests.cs | 7 +- .../StellaOps.Replay.Core.Tests.csproj | 15 +- .../StellaOps.Replay.Tests.csproj | 15 +- .../StellaOps.Router.Integration.Tests.csproj | 42 - ...tellaOps.Router.Transport.Tcp.Tests.csproj | 34 - ...tellaOps.Router.Transport.Tls.Tests.csproj | 31 - ...tellaOps.Router.Transport.Udp.Tests.csproj | 28 - .../CallgraphIngestionTests.cs | 3 +- .../SignalsApiTests.cs | 3 +- .../StellaOps.Signals.Tests.csproj | 17 +- .../SyntheticRuntimeProbeTests.cs | 3 +- .../DeterminismManifestTests.cs | 2 +- .../StellaOps.TestKit.Tests.csproj | 10 +- ...StellaOps.Testing.Determinism.Tests.csproj | 12 +- .../StellaOps.Testing.Manifests.Tests.csproj | 13 +- .../VersionComparisonPropertyTests.cs | 9 +- .../StellaOps.VersionComparison.Tests.csproj | 9 +- src/__Tests/AirGap/README.md | 6 - .../StellaOps.AirGap.Controller.Tests.csproj | 17 - .../FileSystemQuarantineServiceTests.cs | 155 - .../Reconciliation/ArtifactIndexTests.cs | 65 - .../Reconciliation/CycloneDxParserTests.cs | 136 - .../DsseAttestationParserTests.cs | 141 - .../EvidenceDirectoryDiscoveryTests.cs | 65 - .../Reconciliation/Fixtures/sample.cdx.json | 56 - .../Fixtures/sample.intoto.json | 10 - .../Reconciliation/Fixtures/sample.spdx.json | 88 - .../SourcePrecedenceLatticePropertyTests.cs | 453 - .../Reconciliation/SpdxParserTests.cs | 149 - .../ReplayVerifierTests.cs | 76 - .../RootRotationPolicyTests.cs | 44 - .../StellaOps.AirGap.Importer.Tests.csproj | 23 - .../TufMetadataValidatorTests.cs | 46 - .../ImportValidatorIntegrationTests.cs | 204 - .../RekorOfflineReceiptVerifierTests.cs | 165 - .../Versioning/BundleVersionTests.cs | 79 - .../VersionMonotonicityCheckerTests.cs | 157 - .../AdvisoryLinksetTransformerTests.cs | 1 - .../PolicyOverlayTransformerTests.cs | 1 - ...mIngestServiceCollectionExtensionsTests.cs | 1 - .../SbomIngestTransformerTests.cs | 1 - .../StellaOps.Graph.Indexer.Tests.csproj | 15 +- .../VexOverlayTransformerTests.cs | 1 - .../StellaOps.Integration.AirGap.csproj | 18 +- .../BinaryEvidenceDeterminismTests.cs | 4 +- .../FullVerdictPipelineDeterminismTests.cs | 9 +- .../ReachabilityEvidenceDeterminismTests.cs | 2 +- .../StellaOps.Integration.Determinism.csproj | 28 +- .../TriageOutputDeterminismTests.cs | 2 +- .../VerdictArtifactDeterminismTests.cs | 4 +- .../E2EReproducibilityTestFixture.cs | 5 +- .../ManifestComparer.cs | 4 +- .../ReachGraphE2ETests.cs | 434 + .../StellaOps.Integration.E2E.csproj | 52 +- .../StellaOps.Integration.Performance.csproj | 17 +- .../StellaOps.Integration.Platform.csproj | 15 +- .../ProofChainTestFixture.cs | 1 + .../StellaOps.Integration.ProofChain.csproj | 23 +- .../ReachabilityIntegrationTests.cs | 8 +- .../StellaOps.Integration.Reachability.csproj | 23 +- .../StellaOps.Integration.Unknowns.csproj | 21 +- .../Fixtures/hashing/receipt-input.json | 51 - .../Fixtures/hashing/receipt-input.sha256 | 1 - .../Fixtures/cosign.sig | 1 - .../PromotionAttestationBuilderTests.cs | 81 - .../SignersTests.cs | 157 - ...llaOps.Provenance.Attestation.Tests.csproj | 21 - .../TestTimeProvider.cs | 16 - .../VerificationLibraryTests.cs | 80 - .../StellaOps.Replay.Core.Tests.csproj | 13 - .../ReplayTokenSecurityTests.cs | 6 +- .../StellaOps.Audit.ReplayToken.Tests.csproj | 10 +- .../StellaOps.Evidence.Bundle.Tests.csproj | 2 +- .../StellaOps.Gateway.WebService.Tests.csproj | 31 - .../RequestDispatcherTests.cs | 3 +- .../StellaOps.Microservice.Tests.csproj | 24 +- .../TypedEndpointAdapterTests.cs | 3 +- .../FrameTypeTests.cs | 44 - .../RouterConfigTests.cs | 356 - .../StellaOps.Router.Config.Tests.csproj | 33 - .../ConnectionManagerTests.cs | 230 - .../DefaultRoutingPluginTests.cs | 266 - .../InMemoryRoutingStateTests.cs | 144 - .../InMemoryValkeyRateLimitStoreTests.cs | 51 - .../InstanceRateLimiterTests.cs | 50 - .../IntegrationTestAttributes.cs | 40 - .../LimitInheritanceResolverTests.cs | 172 - .../MiddlewareErrorScenarioTests.cs | 225 - .../RoutingDecisionPropertyTests.cs | 756 - .../RateLimitConfigTests.cs | 70 - .../RateLimitMiddlewareTests.cs | 101 - .../RateLimitRouteMatcherTests.cs | 80 - .../RateLimitServiceTests.cs | 110 - .../RouterNodeConfigValidationTests.cs | 45 - .../StellaOps.Router.Gateway.Tests.csproj | 33 - .../ValkeyRateLimitStoreIntegrationTests.cs | 81 - .../ValkeyTestcontainerFixture.cs | 48 - .../CancelFlowTests.cs | 106 - .../HelloHeartbeatFlowTests.cs | 121 - .../InMemoryChannelTests.cs | 117 - .../InMemoryConnectionRegistryTests.cs | 116 - .../RequestResponseFlowTests.cs | 132 - ...Ops.Router.Transport.InMemory.Tests.csproj | 27 - .../StreamingFlowTests.cs | 148 - ...tellaOps.Router.Transport.Udp.Tests.csproj | 27 - .../UdpTransportTests.cs | 539 - .../StellaOps.VulnExplorer.Api.Tests.csproj | 11 +- .../StellaOps.Bench.BinaryLookup.csproj | 4 +- .../VerificationPipelineBenchmarks.cs | 4 +- .../StellaOps.Bench.ProofChain.csproj | 8 +- .../ConcelierPostgresFixture.cs | 2 +- .../ConnectorTestHarness.cs | 2 +- .../StellaOps.Concelier.Testing.csproj | 6 +- .../MigrationTestAttribute.cs | 153 + ...Ops.Infrastructure.Postgres.Testing.csproj | 4 +- .../StellaOps.Messaging.Testing.csproj | 27 - .../StellaOps.Testing.AirGap.csproj | 2 +- .../CanonicalJsonDeterminismProperties.cs | 24 +- .../DigestComputationDeterminismProperties.cs | 6 +- .../FloatingPointStabilityProperties.cs | 40 +- .../JsonObjectArbitraries.cs | 25 +- .../SbomVexOrderingDeterminismProperties.cs | 31 +- ...aOps.Testing.Determinism.Properties.csproj | 13 +- ...icodeNormalizationDeterminismProperties.cs | 22 +- .../StellaOps.Testing.Determinism.csproj | 2 +- .../StellaOps.Testing.Manifests.csproj | 7 +- .../Validation/RunManifestValidator.cs | 2 +- .../StellaOps.Architecture.Tests.csproj | 18 +- .../BackpressureVerificationTests.cs | 5 +- .../RecoveryTests.cs | 2 +- .../StellaOps.Chaos.Router.Tests.csproj | 18 +- .../ValkeyFailureTests.cs | 2 +- .../CycloneDx/CycloneDxRoundTripTests.cs | 2 +- .../StellaOps.Interop.Tests.csproj | 12 +- .../OfflineE2ETests.cs | 2 +- .../StellaOps.Offline.E2E.Tests.csproj | 12 +- .../StellaOps.Parity.Tests.csproj | 30 +- .../CorpusFixtureTests.cs | 3 +- .../FixtureCoverageTests.cs | 3 +- .../ReachabilityLifterTests.cs | 8 +- .../ReachbenchEvaluationHarnessTests.cs | 3 +- .../ReachbenchFixtureTests.cs | 3 +- .../SamplesPublicFixtureTests.cs | 3 +- ...StellaOps.Reachability.FixtureTests.csproj | 13 +- .../DeterministicHashTests.cs | 9 +- .../DsseEnvelopeTests.cs | 6 +- .../ReplayBundleWriterTests.cs | 12 +- .../StellaOps.Replay.Core.Tests.csproj | 13 +- .../ScannerToSignalsReachabilityTests.cs | 3 +- ...Ops.ScannerSignals.IntegrationTests.csproj | 16 +- .../ReachabilityScoringTests.cs | 3 +- .../RuntimeFactsNdjsonReaderTests.cs | 2 +- ...tellaOps.Signals.Reachability.Tests.csproj | 13 +- .../CryptographicFailuresTests.cs | 17 +- .../Infrastructure/SecurityAssertions.cs | 7 +- .../StellaOps.Security.Tests.csproj | 20 +- .../AuditPackExportServiceTests.cs | 230 + .../ReplayAttestationServiceTests.cs | 258 + .../StellaOps.AuditPack.Tests.csproj | 14 +- src/concelier-webservice.slnf | 9 - src/msbuild.pp.xml | 19303 ++++++++++++++++ src/nuget.config | 23 + stryker-thresholds.json | 43 - tools/slntools/__init__.py | 9 + tools/slntools/lib/__init__.py | 37 + .../lib/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 1036 bytes .../__pycache__/csproj_parser.cpython-313.pyc | Bin 0 -> 8801 bytes .../dependency_graph.cpython-313.pyc | Bin 0 -> 8361 bytes .../lib/__pycache__/models.cpython-313.pyc | Bin 0 -> 4580 bytes .../lib/__pycache__/nuget_api.cpython-313.pyc | Bin 0 -> 15053 bytes .../__pycache__/sln_writer.cpython-313.pyc | Bin 0 -> 13126 bytes .../__pycache__/version_utils.cpython-313.pyc | Bin 0 -> 7703 bytes .../vulnerability_models.cpython-313.pyc | Bin 0 -> 6363 bytes tools/slntools/lib/csproj_parser.py | 276 + tools/slntools/lib/dependency_graph.py | 282 + tools/slntools/lib/models.py | 87 + tools/slntools/lib/nuget_api.py | 416 + tools/slntools/lib/sln_writer.py | 381 + tools/slntools/lib/version_utils.py | 237 + tools/slntools/lib/vulnerability_models.py | 123 + tools/slntools/nuget_centralizer.py | 648 + tools/slntools/nuget_normalizer.py | 626 + tools/slntools/nuget_vuln_checker.py | 620 + tools/slntools/sln_generator.py | 395 + 3717 files changed, 264714 insertions(+), 48202 deletions(-) create mode 100644 .actrc create mode 100644 .gitea/README.md create mode 100644 .gitea/config/path-filters.yml create mode 100644 .gitea/docs/architecture.md create mode 100644 .gitea/docs/scripts.md create mode 100644 .gitea/docs/troubleshooting.md create mode 100644 .gitea/scripts/release/bump-service-version.py create mode 100644 .gitea/scripts/release/collect_versions.py create mode 100644 .gitea/scripts/release/generate-docker-tag.sh create mode 100644 .gitea/scripts/release/generate_changelog.py create mode 100644 .gitea/scripts/release/generate_compose.py create mode 100644 .gitea/scripts/release/generate_suite_docs.py create mode 100644 .gitea/scripts/release/read-service-version.sh create mode 100644 .gitea/scripts/release/rollback.sh create mode 100644 .gitea/scripts/test/run-test-category.sh create mode 100644 .gitea/scripts/validate/validate-migrations.sh create mode 100644 .gitea/workflows/container-scan.yml create mode 100644 .gitea/workflows/dependency-license-gate.yml create mode 100644 .gitea/workflows/dependency-security-scan.yml create mode 100644 .gitea/workflows/migration-test.yml create mode 100644 .gitea/workflows/nightly-regression.yml create mode 100644 .gitea/workflows/renovate.yml create mode 100644 .gitea/workflows/rollback.yml create mode 100644 .gitea/workflows/sast-scan.yml create mode 100644 .gitea/workflows/secrets-scan.yml create mode 100644 .gitea/workflows/service-release.yml create mode 100644 .gitea/workflows/templates/replay-verify.yml delete mode 100644 Directory.Build.props delete mode 100644 StellaOps.Router.slnx delete mode 100644 StellaOps.Tests.slnx create mode 100644 build_output_latest.txt create mode 100644 devops/ci-local/.env.local.sample create mode 100644 devops/ci-local/events/pull-request.json create mode 100644 devops/ci-local/events/push-main.json create mode 100644 devops/ci-local/events/release-tag.json create mode 100644 devops/ci-local/events/schedule.json create mode 100644 devops/ci-local/events/workflow-dispatch.json create mode 100644 devops/compose/docker-compose.ci.yaml create mode 100644 devops/compose/postgres-init/02-create-users.sql create mode 100644 devops/compose/postgres-init/03-grant-permissions.sql create mode 100644 devops/docker/repro-builders/BUILD_ENVIRONMENT.md create mode 100644 devops/docker/repro-builders/alpine/Dockerfile create mode 100644 devops/docker/repro-builders/alpine/scripts/build.sh create mode 100644 devops/docker/repro-builders/alpine/scripts/extract-functions.sh create mode 100644 devops/docker/repro-builders/alpine/scripts/normalize.sh create mode 100644 devops/docker/repro-builders/debian/Dockerfile create mode 100644 devops/docker/repro-builders/debian/scripts/build.sh create mode 100644 devops/docker/repro-builders/debian/scripts/extract-functions.sh create mode 100644 devops/docker/repro-builders/debian/scripts/normalize.sh create mode 100644 devops/docker/repro-builders/rhel/Dockerfile create mode 100644 devops/docker/repro-builders/rhel/mock/stellaops-repro.cfg create mode 100644 devops/docker/repro-builders/rhel/scripts/build.sh create mode 100644 devops/docker/repro-builders/rhel/scripts/extract-functions.sh create mode 100644 devops/docker/repro-builders/rhel/scripts/mock-build.sh create mode 100644 devops/docker/repro-builders/rhel/scripts/normalize.sh create mode 100644 devops/releases/service-versions.json create mode 100644 devops/scripts/efcore/Scaffold-AllModules.ps1 create mode 100644 devops/scripts/efcore/Scaffold-Module.ps1 create mode 100644 devops/scripts/efcore/scaffold-all-modules.sh create mode 100644 devops/scripts/efcore/scaffold-module.sh create mode 100644 devops/scripts/fix-duplicate-packages.ps1 create mode 100644 devops/scripts/fix-duplicate-projects.ps1 create mode 100644 devops/scripts/fix-duplicate-using-testkit.ps1 create mode 100644 devops/scripts/fix-missing-xunit.ps1 create mode 100644 devops/scripts/fix-project-references.ps1 create mode 100644 devops/scripts/fix-sln-duplicates.ps1 create mode 100644 devops/scripts/fix-xunit-using.ps1 create mode 100644 devops/scripts/fix-xunit-v3-conflict.ps1 create mode 100644 devops/scripts/generate-plugin-configs.ps1 create mode 100644 devops/scripts/lib/ci-common.sh create mode 100644 devops/scripts/lib/ci-docker.sh create mode 100644 devops/scripts/lib/ci-web.sh create mode 100644 devops/scripts/lib/exit-codes.sh create mode 100644 devops/scripts/lib/git-utils.sh create mode 100644 devops/scripts/lib/hash-utils.sh create mode 100644 devops/scripts/lib/logging.sh create mode 100644 devops/scripts/lib/path-utils.sh create mode 100644 devops/scripts/local-ci.ps1 create mode 100644 devops/scripts/local-ci.sh create mode 100644 devops/scripts/migrations-reset-pre-1.0.sql create mode 100644 devops/scripts/regenerate-solution.ps1 create mode 100644 devops/scripts/remove-stale-refs.ps1 create mode 100644 devops/scripts/restore-deleted-tests.ps1 create mode 100644 devops/scripts/validate-before-commit.ps1 create mode 100644 devops/scripts/validate-before-commit.sh create mode 100644 docs/accessibility/ACCESSIBILITY_AUDIT_VEX_TRUST_COLUMN.md create mode 100644 docs/airgap/VEX_SIGNATURE_VERIFICATION_OFFLINE_MODE.md create mode 100644 docs/attestor/cosign-interop.md create mode 100644 docs/cicd/README.md create mode 100644 docs/cicd/path-filters.md create mode 100644 docs/cicd/release-pipelines.md create mode 100644 docs/cicd/security-scanning.md create mode 100644 docs/cicd/test-strategy.md create mode 100644 docs/cicd/workflow-triggers.md create mode 100644 docs/db/MIGRATION_CONVENTIONS.md create mode 100644 docs/guides/vex-trust-gate-rollout.md create mode 100644 docs/implplan/SPRINT_1229_0001_0001_full_pipeline_validation.md create mode 100644 docs/implplan/SPRINT_1229_000_SBOM_SOURCES_OVERVIEW.md create mode 100644 docs/implplan/SPRINT_1229_001_BE_sbom-sources-foundation.md create mode 100644 docs/implplan/SPRINT_1229_002_BE_sbom-sources-triggers.md create mode 100644 docs/implplan/SPRINT_1229_003_FE_sbom-sources-ui.md rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_015_AI_zastava_companion.md (98%) rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_016_AI_remedy_autopilot.md (98%) rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_017_AI_policy_copilot.md (98%) rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_018_AI_attestations.md (98%) rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_019_AI_offline_inference.md (98%) rename docs/implplan/{ => archived/2025-12-26-completed/ai}/SPRINT_20251226_020_FE_ai_ux_patterns.md (99%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_001_CICD_gitea_scripts.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_002_CICD_devops_consolidation.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_003_CICD_test_matrix.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_004_CICD_module_publishing.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_005_CICD_suite_release.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_006_CICD_local_docker.md (100%) rename docs/implplan/{ => archived/2025-12-26-completed/cicd}/SPRINT_20251226_007_CICD_test_coverage_gap.md (100%) create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/README.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0001_0000_dal_consolidation_master.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0002_0001_dal_notify.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0002_0002_dal_scheduler.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0002_0003_dal_taskrunner.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0003_0001_dal_authority.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0004_0001_dal_scanner.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0005_0001_dal_concelier.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0006_0001_dal_policy.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0006_0002_dal_signals.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0007_0001_dal_excititor.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0007_0002_dal_vexhub.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0007_0003_dal_issuer_directory.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0008_0001_dal_packs_registry.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0008_0002_dal_sbom_service.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0008_0003_dal_airgap.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0009_0001_dal_graph.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0009_0002_dal_evidence.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0010_0001_dal_orchestrator.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0010_0002_dal_evidence_locker.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0010_0003_dal_export_center.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0010_0004_dal_timeline_indexer.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0011_0001_dal_binary_index.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0011_0002_dal_signer.md create mode 100644 docs/implplan/archived/2025-12-27-dal-consolidation/SPRINT_1227_0011_0003_dal_attestor.md create mode 100644 docs/implplan/archived/2025-12-28-docs-consolidation/SPRINT_1228_0001_DOCS_module_documentation_consolidation.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-0005-evidence-first/SPRINT_1227_0005_0001_FE_diff_first_default.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-0005-evidence-first/SPRINT_1227_0005_0002_FE_proof_tree_integration.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-0005-evidence-first/SPRINT_1227_0005_0003_FE_copy_audit_export.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-0005-evidence-first/SPRINT_1227_0005_0004_BE_verdict_replay.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-binary-backport/SPRINT_1227_0000_ADVISORY_binary_backport_fingerprint.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-binary-backport/SPRINT_1227_0001_0001_LB_binary_vex_generator.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-binary-backport/SPRINT_1227_0001_0002_BE_resolution_api.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-binary-backport/SPRINT_1227_0002_0001_LB_reproducible_builders.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-binary-backport/SPRINT_1227_0003_0001_FE_backport_ui.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0004_0001_BE_signature_verification.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0004_0002_FE_trust_column.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0004_0003_BE_vextrust_gate.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0004_0004_LB_trust_attestations.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0004_ADVISORY_vex_trust_verifier.md create mode 100644 docs/implplan/archived/2025-12-28-sprint-vex-trust-verifier/SPRINT_1227_0005_ADVISORY_evidence_first_dashboards.md create mode 100644 docs/implplan/archived/SPRINT_1227_0004_0001_BE_signature_verification.md create mode 100644 docs/implplan/archived/SPRINT_1227_0004_0003_BE_vextrust_gate.md create mode 100644 docs/implplan/archived/SPRINT_1227_0004_0004_LB_trust_attestations.md create mode 100644 docs/implplan/archived/SPRINT_1227_0012_0001_LB_reachgraph_core.md create mode 100644 docs/implplan/archived/SPRINT_1227_0012_0002_BE_reachgraph_store.md create mode 100644 docs/implplan/archived/SPRINT_1227_0012_0003_FE_reachgraph_integration.md create mode 100644 docs/implplan/archived/SPRINT_1227_0013_0001_LB_cyclonedx_cbom.md create mode 100644 docs/implplan/archived/SPRINT_1227_0013_0002_LB_cvss_v4_environmental.md create mode 100644 docs/implplan/archived/SPRINT_1227_0014_0001_BE_stellaverdict_consolidation.md create mode 100644 docs/implplan/archived/SPRINT_1227_0014_0002_FE_verdict_ui.md create mode 100644 docs/implplan/archived/SPRINT_20251226_015_AI_zastava_companion.md create mode 100644 docs/implplan/archived/SPRINT_20251226_016_AI_remedy_autopilot.md create mode 100644 docs/implplan/archived/SPRINT_20251226_017_AI_policy_copilot.md create mode 100644 docs/implplan/archived/SPRINT_20251226_018_AI_attestations.md create mode 100644 docs/implplan/archived/SPRINT_20251226_019_AI_offline_inference.md create mode 100644 docs/implplan/archived/SPRINT_20251226_020_FE_ai_ux_patterns.md create mode 100644 docs/implplan/archived/SPRINT_20251228_001_BE_replay_manifest_ci.md create mode 100644 docs/implplan/archived/SPRINT_20251228_002_BE_oci_attestation_attach.md create mode 100644 docs/implplan/archived/SPRINT_20251228_003_FE_evidence_subgraph_ui.md create mode 100644 docs/implplan/archived/SPRINT_20251228_004_AG_ebpf_runtime_signals.md create mode 100644 docs/implplan/archived/SPRINT_20251228_005_BE_sbom_lineage_graph_i.md create mode 100644 docs/implplan/archived/SPRINT_20251228_006_FE_sbom_lineage_graph_i.md create mode 100644 docs/implplan/archived/SPRINT_20251228_007_BE_sbom_lineage_graph_ii.md create mode 100644 docs/implplan/archived/SPRINT_20251228_008_FE_sbom_lineage_graph_ii.md create mode 100644 docs/modules/README.md create mode 100644 docs/modules/airgap/architecture.md create mode 100644 docs/modules/aoc/README.md create mode 100644 docs/modules/aoc/architecture.md create mode 100644 docs/modules/api/README.md create mode 100644 docs/modules/bench/README.md create mode 100644 docs/modules/cryptography/architecture.md create mode 100644 docs/modules/evidence-locker/architecture.md create mode 100644 docs/modules/feedser/architecture.md create mode 100644 docs/modules/mirror/architecture.md create mode 100644 docs/modules/notifier/README.md create mode 100644 docs/modules/packsregistry/architecture.md create mode 100644 docs/modules/provenance/architecture.md create mode 100644 docs/modules/reachgraph/architecture.md create mode 100644 docs/modules/replay/architecture.md create mode 100644 docs/modules/riskengine/architecture.md create mode 100644 docs/modules/sbomservice/lineage/architecture.md create mode 100644 docs/modules/sbomservice/lineage/schema.sql create mode 100644 docs/modules/symbols/architecture.md create mode 100644 docs/modules/timelineindexer/architecture.md create mode 100644 docs/modules/unknowns/architecture.md create mode 100644 docs/modules/web/architecture.md create mode 100644 docs/product-advisories/archived/2025-12-28_evidence_first_container_security_analysis.md rename docs/product-advisories/{ => archived}/25-Dec-2025 - Building a Deterministic Verdict Engine.md (100%) rename docs/product-advisories/{ => archived}/25-Dec-2025 - Enforcing Canonical JSON for Stable Verdicts.md (100%) rename docs/product-advisories/{ => archived}/25-Dec-2025 - Evolving Evidence Models for Reachability.md (100%) rename docs/product-advisories/{ => archived}/25-Dec-2025 - Planning Keyless Signing for Verdicts.md (100%) rename docs/product-advisories/{ => archived}/26-Dec-2025 - AI Assistant as Proof-Carrying Evidence Engine.md (100%) rename docs/product-advisories/{ => archived}/26-Dec-2025 - AI Surfacing UX Patterns.md (100%) rename docs/product-advisories/{ => archived}/26-Dec-2025 - Stella Ops vNext - SBOM Spine and Deterministic Evidence.md (100%) rename docs/product-advisories/{ => archived}/26-Dec-2025 - Stella Ops vNext - SBOM Spine and Deterministic Evidence.md (100%) rename docs/product-advisories/{ => archived}/26-Dec-2026 - Mapping a Binary Intelligence Graph.md (100%) create mode 100644 docs/product-advisories/archived/27-Dec-2025 - Advisory Lens Gap Analysis and Implementation Plan.md create mode 100644 docs/product-advisories/archived/ADVISORY_SBOM_LINEAGE_GRAPH.md rename docs/product-advisories/{ => archived}/CONSOLIDATED - Deterministic Evidence and Verdict Architecture.md (100%) rename docs/product-advisories/{ => archived}/CONSOLIDATED - Diff-Aware Release Gates and Risk Budgets.md (100%) create mode 100644 docs/replay/replay-manifest-guide.md create mode 100644 docs/router/ARCHITECTURE.md create mode 100644 docs/router/GETTING_STARTED.md create mode 100644 docs/router/README.md create mode 100644 docs/router/transports/README.md create mode 100644 docs/router/transports/development.md create mode 100644 docs/router/transports/inmemory.md create mode 100644 docs/router/transports/rabbitmq.md create mode 100644 docs/router/transports/tcp.md create mode 100644 docs/router/transports/tls.md create mode 100644 docs/router/transports/udp.md create mode 100644 docs/schemas/plugin-config.schema.json create mode 100644 docs/schemas/plugin-manifest.schema.json create mode 100644 docs/schemas/plugin-registry.schema.json create mode 100644 docs/sdks/plugin-development.md create mode 100644 docs/testing/LOCAL_CI_GUIDE.md create mode 100644 docs/testing/PRE_COMMIT_CHECKLIST.md create mode 100644 docs/testing/README.md create mode 100644 fix-asynclifetime.ps1 create mode 100644 fix-fluentassertions.ps1 create mode 100644 fix-npgsql.ps1 create mode 100644 fix-xunit-refs.ps1 create mode 100644 policies/schemas/policy-pack.schema.json create mode 100644 policies/starter-day1.yaml create mode 100644 scripts/fix-duplicate-packages.ps1 create mode 100644 src/AdvisoryAI/StellaOps.AdvisoryAI.WebService/Properties/launchSettings.json create mode 100644 src/AirGap/StellaOps.AirGap.Controller/Properties/launchSettings.json create mode 100644 src/AirGap/StellaOps.AirGap.Importer/StellaOps.AirGap.Importer.csproj.Backup.tmp create mode 100644 src/AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj.Backup.tmp delete mode 100644 src/AirGap/StellaOps.AirGap.Storage.Postgres.Tests/AirGapPostgresFixture.cs delete mode 100644 src/AirGap/StellaOps.AirGap.Storage.Postgres.Tests/StellaOps.AirGap.Storage.Postgres.Tests.csproj delete mode 100644 src/AirGap/StellaOps.AirGap.Storage.Postgres/StellaOps.AirGap.Storage.Postgres.csproj create mode 100644 src/AirGap/StellaOps.AirGap.Time/Properties/launchSettings.json create mode 100644 src/AirGap/StellaOps.AirGap.sln create mode 100644 src/AirGap/__Libraries/StellaOps.AirGap.Persistence/EfCore/Context/AirGapDbContext.cs rename src/AirGap/{StellaOps.AirGap.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.AirGap.Persistence/Extensions/AirGapPersistenceExtensions.cs} (55%) rename src/AirGap/{StellaOps.AirGap.Storage.Postgres => __Libraries/StellaOps.AirGap.Persistence/Postgres}/AirGapDataSource.cs (96%) rename src/AirGap/{StellaOps.AirGap.Storage.Postgres => __Libraries/StellaOps.AirGap.Persistence/Postgres}/Repositories/PostgresAirGapStateStore.cs (99%) rename src/AirGap/{StellaOps.AirGap.Storage.Postgres => __Libraries/StellaOps.AirGap.Persistence/Postgres}/Repositories/PostgresBundleVersionStore.cs (99%) create mode 100644 src/AirGap/__Libraries/StellaOps.AirGap.Persistence/StellaOps.AirGap.Persistence.csproj rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Controller.Tests/AirGapStartupDiagnosticsHostedServiceTests.cs (99%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Controller.Tests/AirGapStateServiceTests.cs (99%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Controller.Tests/InMemoryAirGapStateStoreTests.cs (99%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Controller.Tests/ReplayVerificationServiceTests.cs (99%) create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Controller.Tests/StellaOps.AirGap.Controller.Tests.csproj rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/BundleImportPlannerTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/DsseVerifierTests.cs (98%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/GlobalUsings.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/ImportValidatorTests.cs (99%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/InMemoryBundleRepositoriesTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/MerkleRootCalculatorTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Importer.Tests/OfflineKitMetricsTests.cs (98%) create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Quarantine/FileSystemQuarantineServiceTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/ArtifactIndexTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/CycloneDxParserTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/DsseAttestationParserTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/EvidenceDirectoryDiscoveryTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.cdx.json create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.intoto.json create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.spdx.json create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/SourcePrecedenceLatticePropertyTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Reconciliation/SpdxParserTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/ReplayVerifierTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/RootRotationPolicyTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/TufMetadataValidatorTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Validation/ImportValidatorIntegrationTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Validation/RekorOfflineReceiptVerifierTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Versioning/BundleVersionTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests/Versioning/VersionMonotonicityCheckerTests.cs create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Persistence.Tests/AirGapPostgresFixture.cs rename src/AirGap/{StellaOps.AirGap.Storage.Postgres.Tests => __Tests/StellaOps.AirGap.Persistence.Tests}/AirGapStorageIntegrationTests.cs (92%) rename src/AirGap/{StellaOps.AirGap.Storage.Postgres.Tests => __Tests/StellaOps.AirGap.Persistence.Tests}/PostgresAirGapStateStoreTests.cs (97%) create mode 100644 src/AirGap/__Tests/StellaOps.AirGap.Persistence.Tests/StellaOps.AirGap.Persistence.Tests.csproj rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/AirGapOptionsValidatorTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/GlobalUsings.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/Rfc3161VerifierTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/RoughtimeVerifierTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/SealedStartupValidatorTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/StalenessCalculatorTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/StellaOps.AirGap.Time.Tests.csproj (50%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeAnchorLoaderTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeAnchorPolicyServiceTests.cs (98%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeStatusDtoTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeStatusServiceTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeTelemetryTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeTokenParserTests.cs (100%) rename src/{__Tests/AirGap => AirGap/__Tests}/StellaOps.AirGap.Time.Tests/TimeVerificationServiceTests.cs (100%) delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseCosignCompatibilityTestFixture.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseCosignCompatibilityTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseEnvelopeSerializerTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseNegativeTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseRebundleTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseRoundtripTestFixture.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/DsseRoundtripTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/EnvelopeSignatureServiceTests.cs delete mode 100644 src/Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.Tests/StellaOps.Attestor.Envelope.Tests.csproj create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/Delta/DeltaAttestationService.cs create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/Delta/IDeltaAttestationService.cs create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/Schemas/sbom-delta.v1.schema.json create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/Schemas/verdict-delta.v1.schema.json create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Core/Schemas/vex-delta.v1.schema.json rename src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/Migrations/{ => _archived/pre_1.0}/20251216_001_create_rekor_submission_queue.sql (100%) create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/Migrations/_archived/pre_1.0/README.md create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Infrastructure/StellaOps.Attestor.Infrastructure.csproj.Backup.tmp create mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.WebService/Properties/launchSettings.json delete mode 100644 src/Attestor/StellaOps.Attestor/StellaOps.Attestor.sln create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Bundling/StellaOps.Attestor.Bundling.csproj.Backup.tmp create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.GraphRoot/StellaOps.Attestor.GraphRoot.csproj.Backup.tmp create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Oci/Services/IOciAttestationAttacher.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Oci/Services/IOciRegistryClient.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Oci/Services/OrasAttestationAttacher.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Oci/StellaOps.Attestor.Oci.csproj create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Offline/StellaOps.Attestor.Offline.csproj.Backup.tmp create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/001_initial_schema.sql rename src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/{ => _archived/pre_1.0}/20251214000001_AddProofChainSchema.sql (100%) rename src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/{ => _archived/pre_1.0}/20251214000002_RollbackProofChainSchema.sql (100%) create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations/_archived/pre_1.0/README.md create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Predicates/SbomDeltaPredicate.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Predicates/VerdictDeltaPredicate.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.ProofChain/Predicates/VexDeltaPredicate.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict.Tests/StellaOps.Attestor.TrustVerdict.Tests.csproj create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict.Tests/TrustEvidenceMerkleBuilderTests.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict.Tests/TrustVerdictCacheTests.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict.Tests/TrustVerdictServiceTests.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Caching/TrustVerdictCache.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Evidence/TrustEvidenceMerkleBuilder.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/JsonCanonicalizer.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Migrations/001_create_trust_verdicts.sql create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Oci/TrustVerdictOciAttacher.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Persistence/TrustVerdictRepository.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Predicates/TrustVerdictPredicate.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Services/TrustVerdictService.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/StellaOps.Attestor.TrustVerdict.csproj create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/Telemetry/TrustVerdictMetrics.cs create mode 100644 src/Attestor/__Libraries/StellaOps.Attestor.TrustVerdict/TrustVerdictServiceCollectionExtensions.cs create mode 100644 src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/OciAttestationAttacherIntegrationTests.cs create mode 100644 src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/OciReferenceTests.cs create mode 100644 src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/OrasAttestationAttacherTests.cs create mode 100644 src/Attestor/__Tests/StellaOps.Attestor.Oci.Tests/StellaOps.Attestor.Oci.Tests.csproj create mode 100644 src/Attestor/__Tests/StellaOps.Attestor.TrustVerdict.Tests/TrustVerdictIntegrationTests.cs delete mode 100644 src/Authority/StellaOps.Authority/StellaOps.Auth.Client/bin2/StellaOps.Auth.Abstractions.xml delete mode 100644 src/Authority/StellaOps.Authority/StellaOps.Auth.Client/bin2/StellaOps.Auth.Client.deps.json create mode 100644 src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Oidc/StellaOps.Authority.Plugin.Oidc.csproj.Backup.tmp create mode 100644 src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Saml/StellaOps.Authority.Plugin.Saml.csproj.Backup.tmp create mode 100644 src/Authority/StellaOps.Authority/StellaOps.Authority.Plugins.Abstractions/StellaOps.Authority.Plugins.Abstractions.csproj.Backup.tmp delete mode 100644 src/Authority/StellaOps.Authority/StellaOps.Authority.sln create mode 100644 src/Authority/__Libraries/StellaOps.Authority.Persistence/EfCore/Context/AuthorityDbContext.cs create mode 100644 src/Authority/__Libraries/StellaOps.Authority.Persistence/Extensions/AuthorityPersistenceExtensions.cs rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Documents/AuthorityDocuments.cs (98%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Documents/TokenUsage.cs (87%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Driver/InMemoryDriverShim.cs (100%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Extensions/ServiceCollectionExtensions.cs (91%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Initialization/AuthorityStorageInitializer.cs (89%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Serialization/SerializationAttributes.cs (100%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Serialization/SerializationTypes.cs (100%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Sessions/IClientSessionHandle.cs (94%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Stores/IAuthorityStores.cs (98%) rename src/Authority/{StellaOps.Authority/StellaOps.Authority.Storage.InMemory => __Libraries/StellaOps.Authority.Persistence/InMemory}/Stores/InMemoryStores.cs (99%) create mode 100644 src/Authority/__Libraries/StellaOps.Authority.Persistence/Migrations/001_initial_schema.sql rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres/Migrations => StellaOps.Authority.Persistence/Migrations/_archived/pre_1.0}/001_initial_schema.sql (100%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres/Migrations => StellaOps.Authority.Persistence/Migrations/_archived/pre_1.0}/002_mongo_store_equivalents.sql (100%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres/Migrations => StellaOps.Authority.Persistence/Migrations/_archived/pre_1.0}/003_enable_rls.sql (100%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres/Migrations => StellaOps.Authority.Persistence/Migrations/_archived/pre_1.0}/004_offline_kit_audit.sql (100%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres/Migrations => StellaOps.Authority.Persistence/Migrations/_archived/pre_1.0}/005_verdict_manifests.sql (100%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/AuthorityDataSource.cs (95%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/AirgapAuditEntity.cs (93%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/BootstrapInviteEntity.cs (93%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/ClientEntity.cs (90%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/LoginAttemptEntity.cs (94%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/OfflineKitAuditEntity.cs (88%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/OidcTokenEntity.cs (95%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/RevocationEntity.cs (93%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/RevocationExportStateEntity.cs (84%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/RoleEntity.cs (96%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/ServiceAccountEntity.cs (93%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/SessionEntity.cs (95%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/TenantEntity.cs (96%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/TokenEntity.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Models/UserEntity.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/AirgapAuditRepository.cs (96%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ApiKeyRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/AuditRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/BootstrapInviteRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ClientRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IApiKeyRepository.cs (88%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IAuditRepository.cs (88%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IOfflineKitAuditEmitter.cs (55%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IOfflineKitAuditRepository.cs (77%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IPermissionRepository.cs (91%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IRoleRepository.cs (90%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ISessionRepository.cs (88%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ITenantRepository.cs (91%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ITokenRepository.cs (93%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/IUserRepository.cs (94%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/LoginAttemptRepository.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/OfflineKitAuditEmitter.cs (91%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/OfflineKitAuditRepository.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/OidcTokenRepository.cs (99%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/PermissionRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/RevocationExportStateRepository.cs (95%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/RevocationRepository.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/RoleRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/ServiceAccountRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/SessionRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/TenantRepository.cs (98%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/TokenRepository.cs (99%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/Repositories/UserRepository.cs (99%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/ServiceCollectionExtensions.cs (97%) rename src/Authority/__Libraries/{StellaOps.Authority.Storage.Postgres => StellaOps.Authority.Persistence/Postgres}/VerdictManifestStore.cs (99%) create mode 100644 src/Authority/__Libraries/StellaOps.Authority.Persistence/StellaOps.Authority.Persistence.csproj delete mode 100644 src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/AGENTS.md delete mode 100644 src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/StellaOps.Authority.Storage.Postgres.csproj rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/ApiKeyConcurrencyTests.cs (97%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/ApiKeyIdempotencyTests.cs (91%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/ApiKeyRepositoryTests.cs (96%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/AuditRepositoryTests.cs (96%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/AuthorityMigrationTests.cs (95%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/AuthorityPostgresFixture.cs (97%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/OfflineKitAuditRepositoryTests.cs (95%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/PermissionRepositoryTests.cs (95%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/RefreshTokenRepositoryTests.cs (97%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/RoleBasedAccessTests.cs (98%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/RoleRepositoryTests.cs (94%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/SessionRepositoryTests.cs (94%) create mode 100644 src/Authority/__Tests/StellaOps.Authority.Persistence.Tests/StellaOps.Authority.Persistence.Tests.csproj rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/TestDoubles/InMemoryAuthorityRepositories.cs (98%) rename src/Authority/__Tests/{StellaOps.Authority.Storage.Postgres.Tests => StellaOps.Authority.Persistence.Tests}/TokenRepositoryTests.cs (97%) delete mode 100644 src/Authority/__Tests/StellaOps.Authority.Storage.Postgres.Tests/StellaOps.Authority.Storage.Postgres.Tests.csproj create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/Controllers/ResolutionController.cs create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/Middleware/RateLimitingMiddleware.cs create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/Program.cs create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/Properties/launchSettings.json create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/StellaOps.BinaryIndex.WebService.csproj create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/Telemetry/ResolutionTelemetry.cs create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/appsettings.Development.json create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.WebService/appsettings.json create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.Worker/Jobs/ReproducibleBuildJob.cs create mode 100644 src/BinaryIndex/StellaOps.BinaryIndex.sln create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/BuilderOptions.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/FingerprintClaimModels.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/IFunctionFingerprintExtractor.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/IPatchDiffEngine.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/IReproducibleBuilder.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/PatchDiffEngine.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/ReproducibleBuildJobTypes.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/ServiceCollectionExtensions.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Builders/StellaOps.BinaryIndex.Builders.csproj create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Cache/ResolutionCacheService.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Contracts/Resolution/VulnResolutionContracts.cs rename src/{__Libraries/StellaOps.Router.Common/StellaOps.Router.Common.csproj => BinaryIndex/__Libraries/StellaOps.BinaryIndex.Contracts/StellaOps.BinaryIndex.Contracts.csproj} (67%) create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Core/Resolution/ResolutionService.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/001_initial_schema.sql create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/002_fingerprint_claims.sql rename src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/{ => _archived/pre_1.0}/001_create_binaries_schema.sql (100%) rename src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/{ => _archived/pre_1.0}/002_create_fingerprint_tables.sql (100%) rename src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/{ => _archived/pre_1.0}/003_create_fix_index_tables.sql (100%) rename src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations/{ => _archived/pre_1.0}/20251226_AddFingerprintTables.sql (100%) create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/BinaryMatchEvidenceSchema.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/IDsseSigningAdapter.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/IVexEvidenceGenerator.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/ServiceCollectionExtensions.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/StellaOps.BinaryIndex.VexBridge.csproj create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/VexBridgeOptions.cs create mode 100644 src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.VexBridge/VexEvidenceGenerator.cs create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.Builders.Tests/ReproducibleBuildJobIntegrationTests.cs create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.Builders.Tests/StellaOps.BinaryIndex.Builders.Tests.csproj create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.VexBridge.Tests/StellaOps.BinaryIndex.VexBridge.Tests.csproj create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.VexBridge.Tests/VexBridgeIntegrationTests.cs create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.VexBridge.Tests/VexEvidenceGeneratorTests.cs create mode 100644 src/BinaryIndex/__Tests/StellaOps.BinaryIndex.WebService.Tests/ResolutionControllerIntegrationTests.cs create mode 100644 src/Cartographer/StellaOps.Cartographer/Properties/launchSettings.json create mode 100644 src/Cli/StellaOps.Cli/Commands/AttestCommandGroup.cs create mode 100644 src/Cli/StellaOps.Cli/Commands/CliExitCodes.cs create mode 100644 src/Cli/StellaOps.Cli/Commands/ReachGraph/ReachGraphCommandGroup.cs create mode 100644 src/Cli/StellaOps.Cli/Commands/ReachGraph/ReachGraphCommandHandlers.cs create mode 100644 src/Cli/StellaOps.Cli/Output/OutputRendererExtensions.cs create mode 100644 src/Cli/__Libraries/StellaOps.Cli.Plugins.Verdict/StellaOps.Cli.Plugins.Verdict.csproj create mode 100644 src/Cli/__Libraries/StellaOps.Cli.Plugins.Verdict/VerdictCliCommandModule.cs create mode 100644 src/Cli/src/Cli/StellaOps.Cli/Commands/CliExitCodes.cs create mode 100644 src/Cli/src/Cli/StellaOps.Cli/Output/OutputRendererExtensions.cs delete mode 100644 src/Concelier/Directory.Build.props create mode 100644 src/Concelier/__Libraries/StellaOps.Concelier.Persistence/EfCore/Context/ConcelierDbContext.cs create mode 100644 src/Concelier/__Libraries/StellaOps.Concelier.Persistence/Extensions/ConcelierPersistenceExtensions.cs create mode 100644 src/Concelier/__Libraries/StellaOps.Concelier.Persistence/Migrations/001_initial_schema.sql rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/001_initial_schema.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/002_lnm_linkset_cache.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/004_documents.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/005_connector_state.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/006_partition_merge_events.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/006b_migrate_merge_events_data.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/007_generated_columns_advisories.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/008_sync_ledger.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/009_advisory_canonical.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/010_advisory_source_edge.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/011_canonical_functions.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/012_populate_advisory_canonical.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/013_populate_advisory_source_edge.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/014_verify_canonical_migration.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/015_interest_score.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/016_sbom_registry.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/Migrations => StellaOps.Concelier.Persistence/Migrations/_archived/pre_1.0}/017_provenance_scope.sql (100%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Advisories/IPostgresAdvisoryStore.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Advisories/PostgresAdvisoryStore.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/ConcelierDataSource.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/ContractsMappingExtensions.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Conversion/AdvisoryConversionResult.cs (94%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Conversion/AdvisoryConverter.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/DocumentStore.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryAffectedEntity.cs (91%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryAliasEntity.cs (87%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryCanonicalEntity.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryCreditEntity.cs (86%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryCvssEntity.cs (91%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryEntity.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryLinksetCacheEntity.cs (92%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryReferenceEntity.cs (85%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisorySnapshotEntity.cs (86%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisorySourceEdgeEntity.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/AdvisoryWeaknessEntity.cs (86%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/DocumentRecordEntity.cs (86%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/FeedSnapshotEntity.cs (87%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/KevFlagEntity.cs (91%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/MergeEventEntity.cs (87%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/ProvenanceScopeEntity.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/SitePolicyEntity.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/SourceEntity.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/SourceStateEntity.cs (90%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Models/SyncLedgerEntity.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryAffectedRepository.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryAliasRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryCanonicalRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryCreditRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryCvssRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryLinksetCacheRepository.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryReferenceRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisorySnapshotRepository.cs (95%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/AdvisoryWeaknessRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/DocumentRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/FeedSnapshotRepository.cs (95%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryAffectedRepository.cs (86%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryAliasRepository.cs (80%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryCanonicalRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryCreditRepository.cs (75%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryCvssRepository.cs (75%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryReferenceRepository.cs (76%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisorySnapshotRepository.cs (76%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IAdvisoryWeaknessRepository.cs (76%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IFeedSnapshotRepository.cs (75%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IKevFlagRepository.cs (79%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IMergeEventRepository.cs (76%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/IProvenanceScopeRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/ISourceRepository.cs (81%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/ISourceStateRepository.cs (74%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/ISyncLedgerRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/InterestScoreRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/KevFlagRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/MergeEventRepository.cs (95%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresChangeHistoryStore.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresDtoStore.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresExportStateStore.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresJpFlagStore.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresProvenanceScopeStore.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/PostgresPsirtFlagStore.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/ProvenanceScopeRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/SbomRegistryRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/SourceRepository.cs (97%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/SourceStateRepository.cs (96%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Repositories/SyncLedgerRepository.cs (99%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/ServiceCollectionExtensions.cs (93%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/SourceStateAdapter.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres => StellaOps.Concelier.Persistence/Postgres}/Sync/SitePolicyEnforcementService.cs (98%) rename src/Concelier/__Libraries/{StellaOps.Concelier.Storage.Postgres/StellaOps.Concelier.Storage.Postgres.csproj => StellaOps.Concelier.Persistence/StellaOps.Concelier.Persistence.csproj} (55%) delete mode 100644 src/Concelier/__Libraries/StellaOps.Concelier.Storage.Postgres/AGENTS.md delete mode 100644 src/Concelier/__Libraries/StellaOps.Concelier.Storage.Postgres/Converters/Importers/ParityRunner.cs create mode 100644 src/Concelier/__Tests/StellaOps.Concelier.Models.Tests/Fixtures/ghsa-semver.actual.json create mode 100644 src/Concelier/__Tests/StellaOps.Concelier.Models.Tests/Fixtures/kev-flag.actual.json create mode 100644 src/Concelier/__Tests/StellaOps.Concelier.Models.Tests/Fixtures/nvd-basic.actual.json create mode 100644 src/Concelier/__Tests/StellaOps.Concelier.Models.Tests/Fixtures/psirt-overlay.actual.json rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/AdvisoryCanonicalRepositoryTests.cs (99%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/AdvisoryIdempotencyTests.cs (98%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/AdvisoryRepositoryTests.cs (98%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/ConcelierMigrationTests.cs (99%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/ConcelierPostgresFixture.cs (90%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/ConcelierQueryDeterminismTests.cs (98%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/InterestScoreRepositoryTests.cs (99%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/InterestScoringServiceIntegrationTests.cs (99%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/KevFlagRepositoryTests.cs (97%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/Linksets/AdvisoryLinksetCacheRepositoryTests.cs (97%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/MergeEventRepositoryTests.cs (97%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/Performance/AdvisoryPerformanceTests.cs (98%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/ProvenanceScopeRepositoryTests.cs (98%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/RepositoryIntegrationTests.cs (97%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/SourceRepositoryTests.cs (96%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/SourceStateRepositoryTests.cs (96%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests/StellaOps.Concelier.Storage.Postgres.Tests.csproj => StellaOps.Concelier.Persistence.Tests/StellaOps.Concelier.Persistence.Tests.csproj} (54%) rename src/Concelier/__Tests/{StellaOps.Concelier.Storage.Postgres.Tests => StellaOps.Concelier.Persistence.Tests}/SyncLedgerRepositoryTests.cs (98%) rename src/{ => Cryptography}/StellaOps.Cryptography.sln (60%) create mode 100644 src/Directory.Packages.props create mode 100644 src/Directory.Versions.props create mode 100644 src/EvidenceLocker/StellaOps.EvidenceLocker/Properties/launchSettings.json delete mode 100644 src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.sln create mode 100644 src/Excititor/StellaOps.Excititor.WebService/Properties/launchSettings.json create mode 100644 src/Excititor/StellaOps.Excititor.WebService/Services/VexSignatureVerifierV1Adapter.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Dsse/DsseEnvelope.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Observations/VexDeltaModels.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/CryptoProfileSelector.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/IVexSignatureVerifierV2.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/IssuerDirectoryClient.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/ProductionVexSignatureVerifier.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/VerificationCacheService.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/VexSignatureVerifierOptions.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/VexVerificationMetrics.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/VexVerificationModels.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Core/Verification/VexVerificationServiceCollectionExtensions.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Persistence/EfCore/Context/ExcititorDbContext.cs rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/ServiceCollectionExtensions.cs => StellaOps.Excititor.Persistence/Extensions/ExcititorPersistenceExtensions.cs} (86%) create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Persistence/Migrations/001_initial_schema.sql rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/001_initial_schema.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/002_vex_raw_store.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/003_enable_rls.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/004_generated_columns_vex.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/005_partition_timeline_events.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/005b_migrate_timeline_events_data.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres/Migrations => StellaOps.Excititor.Persistence/Migrations/_archived/pre_1.0}/006_calibration.sql (100%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/ExcititorDataSource.cs (96%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Models/ProjectEntity.cs (96%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Models/VexStatementEntity.cs (98%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/IVexStatementRepository.cs (95%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresAppendOnlyCheckpointStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresAppendOnlyLinksetStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresConnectorStateRepository.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresVexAttestationStore.cs (99%) create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Persistence/Postgres/Repositories/PostgresVexDeltaRepository.cs rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresVexObservationStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresVexProviderStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresVexRawStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/PostgresVexTimelineEventStore.cs (99%) rename src/Excititor/__Libraries/{StellaOps.Excititor.Storage.Postgres => StellaOps.Excititor.Persistence/Postgres}/Repositories/VexStatementRepository.cs (99%) create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Persistence/Repositories/IVexDeltaRepository.cs create mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Persistence/StellaOps.Excititor.Persistence.csproj delete mode 100644 src/Excititor/__Libraries/StellaOps.Excititor.Storage.Postgres/StellaOps.Excititor.Storage.Postgres.csproj create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Core.Tests/Verification/ProductionVexSignatureVerifierTests.cs create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Core.UnitTests/Observations/VexLinksetTests.cs rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/ExcititorMigrationTests.cs (99%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/ExcititorPostgresFixture.cs (93%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/PostgresAppendOnlyLinksetStoreTests.cs (96%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/PostgresVexAttestationStoreTests.cs (97%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/PostgresVexObservationStoreTests.cs (98%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/PostgresVexProviderStoreTests.cs (97%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/PostgresVexTimelineEventStoreTests.cs (97%) create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Persistence.Tests/StellaOps.Excititor.Persistence.Tests.csproj rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/VexQueryDeterminismTests.cs (96%) rename src/Excititor/__Tests/{StellaOps.Excititor.Storage.Postgres.Tests => StellaOps.Excititor.Persistence.Tests}/VexStatementIdempotencyTests.cs (96%) create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Plugin.Tests/PluginCatalogTests.cs create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Plugin.Tests/StellaOps.Excititor.Plugin.Tests.csproj create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Plugin.Tests/VexConnectorRegistrationTests.cs delete mode 100644 src/Excititor/__Tests/StellaOps.Excititor.Storage.Postgres.Tests/StellaOps.Excititor.Storage.Postgres.Tests.csproj create mode 100644 src/Excititor/__Tests/StellaOps.Excititor.WebService.Tests/VerificationIntegrationTests.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Core/Domain/LineageEvidencePack.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Core/Services/EvidencePackSigningService.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Core/Services/IEvidencePackSigningService.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Core/Services/ILineageEvidencePackService.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Core/Services/LineageEvidencePackService.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/Lineage/LineageExportEndpoints.cs create mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.WebService/Lineage/LineageExportServiceExtensions.cs delete mode 100644 src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.sln create mode 100644 src/Feedser/StellaOps.Feedser.sln delete mode 100644 src/Findings/StellaOps.Findings.Ledger.Tests/Directory.Build.props create mode 100644 src/Findings/StellaOps.Findings.Ledger.WebService/Properties/launchSettings.json create mode 100644 src/Findings/StellaOps.Findings.sln create mode 100644 src/Gateway/StellaOps.Gateway.WebService/Properties/launchSettings.json create mode 100644 src/Gateway/StellaOps.Gateway.sln rename src/{ => Gateway}/__Tests/StellaOps.Gateway.WebService.Tests/Authorization/AuthorizationMiddlewareTests.cs (99%) rename src/{ => Gateway}/__Tests/StellaOps.Gateway.WebService.Tests/Authorization/EffectiveClaimsStoreTests.cs (99%) create mode 100644 src/Gateway/__Tests/StellaOps.Gateway.WebService.Tests/GatewayHealthTests.cs create mode 100644 src/Graph/StellaOps.Graph.Api/Contracts/ReachabilityContracts.cs create mode 100644 src/Graph/StellaOps.Graph.Api/Properties/launchSettings.json create mode 100644 src/Graph/StellaOps.Graph.Api/Services/IReachabilityDeltaService.cs create mode 100644 src/Graph/StellaOps.Graph.Api/Services/InMemoryReachabilityDeltaService.cs delete mode 100644 src/Graph/StellaOps.Graph.Indexer.Storage.Postgres.Tests/GraphIndexerPostgresFixture.cs delete mode 100644 src/Graph/StellaOps.Graph.Indexer.Storage.Postgres.Tests/StellaOps.Graph.Indexer.Storage.Postgres.Tests.csproj delete mode 100644 src/Graph/StellaOps.Graph.Indexer.Storage.Postgres/StellaOps.Graph.Indexer.Storage.Postgres.csproj create mode 100644 src/Graph/StellaOps.Graph.sln create mode 100644 src/Graph/__Libraries/StellaOps.Graph.Indexer.Persistence/EfCore/Context/GraphIndexerDbContext.cs rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.Graph.Indexer.Persistence/Extensions/GraphIndexerPersistenceExtensions.cs} (62%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres => __Libraries/StellaOps.Graph.Indexer.Persistence/Postgres}/GraphIndexerDataSource.cs (95%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres => __Libraries/StellaOps.Graph.Indexer.Persistence/Postgres}/Repositories/PostgresGraphAnalyticsWriter.cs (99%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres => __Libraries/StellaOps.Graph.Indexer.Persistence/Postgres}/Repositories/PostgresGraphDocumentWriter.cs (99%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres => __Libraries/StellaOps.Graph.Indexer.Persistence/Postgres}/Repositories/PostgresGraphSnapshotProvider.cs (98%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres => __Libraries/StellaOps.Graph.Indexer.Persistence/Postgres}/Repositories/PostgresIdempotencyStore.cs (97%) create mode 100644 src/Graph/__Libraries/StellaOps.Graph.Indexer.Persistence/StellaOps.Graph.Indexer.Persistence.csproj create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Persistence.Tests/GraphIndexerPostgresFixture.cs rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres.Tests => __Tests/StellaOps.Graph.Indexer.Persistence.Tests}/GraphQueryDeterminismTests.cs (97%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres.Tests => __Tests/StellaOps.Graph.Indexer.Persistence.Tests}/GraphStorageMigrationTests.cs (97%) rename src/Graph/{StellaOps.Graph.Indexer.Storage.Postgres.Tests => __Tests/StellaOps.Graph.Indexer.Persistence.Tests}/PostgresIdempotencyStoreTests.cs (94%) create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Persistence.Tests/StellaOps.Graph.Indexer.Persistence.Tests.csproj create mode 100644 src/Graph/__Tests/StellaOps.Graph.Indexer.Tests/GraphTestHelpers.cs create mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory.sln delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres/StellaOps.IssuerDirectory.Storage.Postgres.csproj create mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.WebService/Properties/launchSettings.json delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.sln delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/IssuerDirectoryPostgresFixture.cs delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/IssuerKeyRepositoryTests.cs delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/IssuerRepositoryTests.cs delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/IssuerTrustRepositoryTests.cs delete mode 100644 src/IssuerDirectory/StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests.csproj create mode 100644 src/IssuerDirectory/__Libraries/StellaOps.IssuerDirectory.Persistence/EfCore/Context/IssuerDirectoryDbContext.cs rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.IssuerDirectory.Persistence/Extensions/IssuerDirectoryPersistenceExtensions.cs} (84%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence}/Migrations/001_initial_schema.sql (100%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence/Postgres}/IssuerDirectoryDataSource.cs (95%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence/Postgres}/Repositories/PostgresIssuerAuditSink.cs (97%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence/Postgres}/Repositories/PostgresIssuerKeyRepository.cs (99%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence/Postgres}/Repositories/PostgresIssuerRepository.cs (99%) rename src/IssuerDirectory/{StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres => __Libraries/StellaOps.IssuerDirectory.Persistence/Postgres}/Repositories/PostgresIssuerTrustRepository.cs (98%) create mode 100644 src/IssuerDirectory/__Libraries/StellaOps.IssuerDirectory.Persistence/StellaOps.IssuerDirectory.Persistence.csproj create mode 100644 src/IssuerDirectory/__Libraries/StellaOps.IssuerDirectory.Persistence/StellaOps.IssuerDirectory.Persistence.csproj.Backup.tmp rename src/IssuerDirectory/{StellaOps.IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests => __Tests/StellaOps.IssuerDirectory.Persistence.Tests}/IssuerAuditSinkTests.cs (98%) create mode 100644 src/IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Persistence.Tests/IssuerDirectoryPostgresCollection.cs rename src/IssuerDirectory/__Tests/{StellaOps.IssuerDirectory.Storage.Postgres.Tests => StellaOps.IssuerDirectory.Persistence.Tests}/IssuerDirectoryPostgresFixture.cs (67%) create mode 100644 src/IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Persistence.Tests/IssuerKeyRepositoryTests.cs rename src/IssuerDirectory/__Tests/{StellaOps.IssuerDirectory.Storage.Postgres.Tests => StellaOps.IssuerDirectory.Persistence.Tests}/IssuerRepositoryTests.cs (69%) create mode 100644 src/IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Persistence.Tests/StellaOps.IssuerDirectory.Persistence.Tests.csproj rename src/IssuerDirectory/__Tests/{StellaOps.IssuerDirectory.Storage.Postgres.Tests => StellaOps.IssuerDirectory.Persistence.Tests}/TrustRepositoryTests.cs (54%) delete mode 100644 src/IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/IssuerKeyRepositoryTests.cs delete mode 100644 src/IssuerDirectory/__Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests/StellaOps.IssuerDirectory.Storage.Postgres.Tests.csproj delete mode 100644 src/Notifier/StellaOps.Notifier/StellaOps.Notifier.sln create mode 100644 src/Notify/StellaOps.Notify.WebService/Properties/launchSettings.json create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/EfCore/Context/NotifyDbContext.cs rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres/ServiceCollectionExtensions.cs => StellaOps.Notify.Persistence/Extensions/NotifyPersistenceExtensions.cs} (58%) create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/InMemory/Documents/NotifyDocuments.cs create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/InMemory/Repositories/INotifyRepositories.cs create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/InMemory/Repositories/InMemoryRepositories.cs create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/Migrations/001_initial_schema.sql rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/ChannelEntity.cs (97%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/DeliveryEntity.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/DigestEntity.cs (94%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/EscalationEntity.cs (96%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/InboxEntity.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/IncidentEntity.cs (96%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/LocalizationBundleEntity.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/LockEntity.cs (88%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/MaintenanceWindowEntity.cs (91%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/NotifyAuditEntity.cs (90%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/OnCallScheduleEntity.cs (94%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/OperatorOverrideEntity.cs (91%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/QuietHoursEntity.cs (92%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/RuleEntity.cs (92%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/TemplateEntity.cs (91%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Models/ThrottleConfigEntity.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/NotifyDataSource.cs (95%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/ChannelRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/DeliveryRepository.cs (99%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/DigestRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/EscalationRepository.cs (99%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IChannelRepository.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IDeliveryRepository.cs (96%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IDigestRepository.cs (90%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IEscalationRepository.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IInboxRepository.cs (90%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IIncidentRepository.cs (90%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/ILocalizationBundleRepository.cs (94%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/ILockRepository.cs (85%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IMaintenanceWindowRepository.cs (88%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/INotifyAuditRepository.cs (86%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IOnCallScheduleRepository.cs (86%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IOperatorOverrideRepository.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IQuietHoursRepository.cs (86%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IRuleRepository.cs (87%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/ITemplateRepository.cs (87%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IThrottleConfigRepository.cs (93%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/InboxRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/IncidentRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/LocalizationBundleRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/LockRepository.cs (95%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/MaintenanceWindowRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/NotifyAuditRepository.cs (97%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/OnCallScheduleRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/OperatorOverrideRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/QuietHoursRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/RuleRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/TemplateRepository.cs (98%) rename src/Notify/__Libraries/{StellaOps.Notify.Storage.Postgres => StellaOps.Notify.Persistence/Postgres}/Repositories/ThrottleConfigRepository.cs (98%) create mode 100644 src/Notify/__Libraries/StellaOps.Notify.Persistence/StellaOps.Notify.Persistence.csproj delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/AGENTS.md delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations/001_initial_schema.sql delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations/010_enable_rls.sql delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations/011_partition_deliveries.sql delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations/011b_migrate_deliveries_data.sql delete mode 100644 src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/StellaOps.Notify.Storage.Postgres.csproj rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/ChannelRepositoryTests.cs (75%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/DeliveryIdempotencyTests.cs (97%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/DeliveryRepositoryTests.cs (96%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/DigestAggregationTests.cs (77%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/DigestRepositoryTests.cs (96%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/EscalationHandlingTests.cs (78%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/InboxRepositoryTests.cs (70%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/NotificationDeliveryFlowTests.cs (80%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/NotifyAuditRepositoryTests.cs (79%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/NotifyMigrationTests.cs (87%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/NotifyPostgresFixture.cs (87%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/RetryStatePersistenceTests.cs (97%) rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/RuleRepositoryTests.cs (77%) create mode 100644 src/Notify/__Tests/StellaOps.Notify.Persistence.Tests/StellaOps.Notify.Persistence.Tests.csproj rename src/Notify/__Tests/{StellaOps.Notify.Storage.Postgres.Tests => StellaOps.Notify.Persistence.Tests}/TemplateRepositoryTests.cs (75%) delete mode 100644 src/Notify/__Tests/StellaOps.Notify.Storage.Postgres.Tests/StellaOps.Notify.Storage.Postgres.Tests.csproj create mode 100644 src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Infrastructure/StellaOps.Orchestrator.Infrastructure.csproj.Backup.tmp delete mode 100644 src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.sln create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry.sln.bak create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry.slnx rename src/{__Libraries/StellaOps.Microservice/StellaOps.Microservice.csproj => PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Infrastructure/StellaOps.PacksRegistry.Infrastructure.csproj.Backup.tmp} (71%) create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Persistence.EfCore/Context/PacksRegistryDbContext.cs create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Persistence.EfCore/Extensions/PacksRegistryPersistenceExtensions.cs create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Persistence.EfCore/README.md create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Persistence.EfCore/StellaOps.PacksRegistry.Persistence.EfCore.csproj delete mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres.Tests/StellaOps.PacksRegistry.Storage.Postgres.Tests.csproj delete mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres/StellaOps.PacksRegistry.Storage.Postgres.csproj create mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.WebService/Properties/launchSettings.json delete mode 100644 src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.sln create mode 100644 src/PacksRegistry/__Libraries/StellaOps.PacksRegistry.Persistence/EfCore/Context/PacksRegistryDbContext.cs rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.PacksRegistry.Persistence/Extensions/PacksRegistryPersistenceExtensions.cs} (65%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/PacksRegistryDataSource.cs (95%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresAttestationRepository.cs (98%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresAuditRepository.cs (98%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresLifecycleRepository.cs (98%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresMirrorRepository.cs (98%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresPackRepository.cs (99%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres => __Libraries/StellaOps.PacksRegistry.Persistence/Postgres}/Repositories/PostgresParityRepository.cs (98%) create mode 100644 src/PacksRegistry/__Libraries/StellaOps.PacksRegistry.Persistence/StellaOps.PacksRegistry.Persistence.csproj rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres.Tests => __Tests/StellaOps.PacksRegistry.Persistence.Tests}/PacksRegistryPostgresFixture.cs (89%) rename src/PacksRegistry/{StellaOps.PacksRegistry/StellaOps.PacksRegistry.Storage.Postgres.Tests => __Tests/StellaOps.PacksRegistry.Persistence.Tests}/PostgresPackRepositoryTests.cs (97%) create mode 100644 src/PacksRegistry/__Tests/StellaOps.PacksRegistry.Persistence.Tests/StellaOps.PacksRegistry.Persistence.Tests.csproj create mode 100644 src/Policy/StellaOps.Policy.Engine/Confidence/VexTrustConfidenceFactorProvider.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Crypto/CryptoRiskAtoms.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Crypto/CryptoRiskEvaluator.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/DependencyInjection/VexTrustGateServiceCollectionExtensions.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Gates/VexTrustGate.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Gates/VexTrustGateMetrics.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Gates/VexTrustGateOptions.cs create mode 100644 src/Policy/StellaOps.Policy.Engine/Properties/launchSettings.json create mode 100644 src/Policy/StellaOps.Policy.Engine/Services/VerdictLinkService.cs create mode 100644 src/Policy/StellaOps.Policy.Gateway/Properties/launchSettings.json delete mode 100644 src/Policy/StellaOps.Policy.only.sln create mode 100644 src/Policy/__Libraries/StellaOps.Policy.Persistence/EfCore/Context/PolicyDbContext.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy.Persistence/Extensions/PolicyPersistenceExtensions.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy.Persistence/Migrations/001_initial_schema.sql rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/002_cvss_receipts.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/003_snapshots_violations.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/004_epss_risk_scores.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/005_cvss_multiversion.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/006_enable_rls.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/007_unknowns_registry.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/008_exception_objects.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/009_exception_applications.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/010_recheck_evidence.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/010_unknowns_blast_radius_containment.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/011_unknowns_reason_codes.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/012_budget_ledger.sql (100%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/Migrations => StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0}/013_exception_approval.sql (100%) create mode 100644 src/Policy/__Libraries/StellaOps.Policy.Persistence/Migrations/_archived/pre_1.0/README.md rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Migration/LegacyDocumentConverter.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Migration/PolicyMigrator.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/BudgetLedgerEntity.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/ConflictEntity.cs (93%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/EvaluationRunEntity.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/ExceptionApprovalEntity.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/ExceptionEntity.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/ExplanationEntity.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/LedgerExportEntity.cs (93%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/PackEntity.cs (96%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/PackVersionEntity.cs (96%) create mode 100644 src/Policy/__Libraries/StellaOps.Policy.Persistence/Postgres/Models/PolicyAuditEntity.cs rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/RiskProfileEntity.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/RuleEntity.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/SnapshotEntity.cs (90%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/ViolationEventEntity.cs (92%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Models/WorkerResultEntity.cs (94%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/PolicyDataSource.cs (95%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ConflictRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/EvaluationRunRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ExceptionApprovalRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ExceptionRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ExplanationRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IConflictRepository.cs (93%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IEvaluationRunRepository.cs (96%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IExceptionApprovalRepository.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IExceptionRepository.cs (95%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IExplanationRepository.cs (88%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ILedgerExportRepository.cs (94%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IPackRepository.cs (94%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IPackVersionRepository.cs (93%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IPolicyAuditRepository.cs (87%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IRiskProfileRepository.cs (95%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IRuleRepository.cs (94%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ISnapshotRepository.cs (92%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IViolationEventRepository.cs (94%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/IWorkerResultRepository.cs (95%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/LedgerExportRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PackRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PackVersionRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PolicyAuditRepository.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PostgresBudgetStore.cs (97%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PostgresExceptionObjectRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/PostgresReceiptRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/RiskProfileRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/RuleRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/SnapshotRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/ViolationEventRepository.cs (98%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/Repositories/WorkerResultRepository.cs (99%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres => StellaOps.Policy.Persistence/Postgres}/ServiceCollectionExtensions.cs (92%) rename src/Policy/__Libraries/{StellaOps.Policy.Storage.Postgres/StellaOps.Policy.Storage.Postgres.csproj => StellaOps.Policy.Persistence/StellaOps.Policy.Persistence.csproj} (52%) delete mode 100644 src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/AGENTS.md delete mode 100644 src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/Migrations/001_initial_schema.sql delete mode 100644 src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/Models/PolicyAuditEntity.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy/Crypto/CryptoAsset.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy/Crypto/CryptoAtoms.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy/Crypto/CryptoRiskRules.cs create mode 100644 src/Policy/__Libraries/StellaOps.Policy/StellaOps.Policy.csproj.Backup.tmp create mode 100644 src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Crypto/CryptoRiskEvaluatorTests.cs create mode 100644 src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Gates/VexTrustConfidenceFactorProviderTests.cs create mode 100644 src/Policy/__Tests/StellaOps.Policy.Engine.Tests/Gates/VexTrustGateTests.cs create mode 100644 src/Policy/__Tests/StellaOps.Policy.Gateway.Tests/VexTrustGateIntegrationTests.cs rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/EvaluationRunRepositoryTests.cs (97%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/ExceptionObjectRepositoryTests.cs (98%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/ExceptionRepositoryTests.cs (97%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PackRepositoryTests.cs (97%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PackVersioningWorkflowTests.cs (98%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PolicyAuditRepositoryTests.cs (96%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PolicyMigrationTests.cs (99%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PolicyPostgresFixture.cs (90%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PolicyQueryDeterminismTests.cs (91%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PolicyVersioningImmutabilityTests.cs (93%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PostgresExceptionApplicationRepositoryTests.cs (98%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PostgresExceptionObjectRepositoryTests.cs (99%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/PostgresReceiptRepositoryTests.cs (94%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/RecheckEvidenceMigrationTests.cs (93%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/RiskProfileRepositoryTests.cs (98%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/RiskProfileVersionHistoryTests.cs (98%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/RuleRepositoryTests.cs (97%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests/StellaOps.Policy.Storage.Postgres.Tests.csproj => StellaOps.Policy.Persistence.Tests/StellaOps.Policy.Persistence.Tests.csproj} (50%) rename src/Policy/__Tests/{StellaOps.Policy.Storage.Postgres.Tests => StellaOps.Policy.Persistence.Tests}/UnknownsRepositoryTests.cs (97%) create mode 100644 src/Policy/__Tests/StellaOps.Policy.Scoring.Tests/CvssV4EnvironmentalTests.cs create mode 100644 src/Policy/__Tests/StellaOps.Policy.Scoring.Tests/Fixtures/hashing/receipt-input.json create mode 100644 src/Policy/__Tests/StellaOps.Policy.Scoring.Tests/Fixtures/hashing/receipt-input.sha256 delete mode 100644 src/Provenance/StellaOps.Provenance.Attestation.Tool/Directory.Build.props create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/SignersTests.cs create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/TestTimeProvider.cs rename src/{__Tests/Provenance => Provenance/__Tests}/StellaOps.Provenance.Attestation.Tests/ToolEntrypointTests.cs (95%) create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/ToolEntrypointTests.cs.utf8 create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/VerificationLibraryTests.cs create mode 100644 src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests/VerificationLibraryTests.cs.utf8 create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Controllers/ReachGraphController.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Models/ReachGraphContracts.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Program.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Properties/launchSettings.json create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/IReachGraphReplayService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/IReachGraphSliceService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/IReachGraphStoreService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/PaginationService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/ReachGraphReplayService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/ReachGraphSliceService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/Services/ReachGraphStoreService.cs create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/StellaOps.ReachGraph.WebService.csproj create mode 100644 src/ReachGraph/StellaOps.ReachGraph.WebService/openapi.yaml create mode 100644 src/ReachGraph/StellaOps.ReachGraph.sln create mode 100644 src/ReachGraph/__Tests/StellaOps.ReachGraph.WebService.Tests/ReachGraphApiIntegrationTests.cs create mode 100644 src/ReachGraph/__Tests/StellaOps.ReachGraph.WebService.Tests/StellaOps.ReachGraph.WebService.Tests.csproj create mode 100644 src/Replay/StellaOps.Replay.WebService/Properties/launchSettings.json create mode 100644 src/Replay/StellaOps.Replay.WebService/VerdictReplayEndpoints.cs create mode 100644 src/Replay/StellaOps.Replay.sln rename src/{__Tests/Replay => Replay/__Tests}/StellaOps.Replay.Core.Tests/PolicySimulationInputLockValidatorTests.cs (100%) create mode 100644 src/Replay/__Tests/StellaOps.Replay.Core.Tests/StellaOps.Replay.Core.Tests.csproj create mode 100644 src/Replay/__Tests/StellaOps.Replay.Core.Tests/VerdictReplayEndpointsTests.cs create mode 100644 src/Replay/__Tests/StellaOps.Replay.Core.Tests/VerdictReplayIntegrationTests.cs delete mode 100644 src/RiskEngine/StellaOps.RiskEngine/StellaOps.RiskEngine.sln create mode 100644 src/Router/AGENTS.md create mode 100644 src/Router/StellaOps.Gateway.WebService/Authorization/AuthorizationMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Authorization/EffectiveClaimsStore.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Authorization/IEffectiveClaimsStore.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Configuration/GatewayOptions.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Configuration/GatewayOptionsValidator.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Configuration/GatewayValueParser.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Dockerfile create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/ClaimsPropagationMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/CorrelationIdMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/GatewayContextKeys.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/GatewayRoutes.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/HealthCheckMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/IdentityHeaderPolicyMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/RequestRoutingMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/SenderConstraintMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Middleware/TenantMiddleware.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Program.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Properties/launchSettings.json create mode 100644 src/Router/StellaOps.Gateway.WebService/Security/AllowAllAuthenticationHandler.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Services/GatewayHealthMonitorService.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Services/GatewayHostedService.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Services/GatewayMetrics.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Services/GatewayServiceStatus.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/Services/GatewayTransportClient.cs create mode 100644 src/Router/StellaOps.Gateway.WebService/StellaOps.Gateway.WebService.csproj create mode 100644 src/Router/StellaOps.Gateway.WebService/appsettings.Development.json create mode 100644 src/Router/StellaOps.Gateway.WebService/appsettings.json create mode 100644 src/Router/StellaOps.Router.sln rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryAtomicTokenStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryCacheFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryCacheStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryEventStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryIdempotencyStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryMessageLease.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryMessageQueue.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryMessageQueueFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryQueueRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemorySetStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemorySortedIndex.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/InMemoryTransportPlugin.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.InMemory/StellaOps.Messaging.Transport.InMemory.csproj (82%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/Options/PostgresTransportOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresAtomicTokenStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresCacheFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresCacheStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresConnectionFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresEventStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresIdempotencyStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresMessageLease.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresMessageQueue.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresMessageQueueFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresSetStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresSortedIndex.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/PostgresTransportPlugin.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Postgres/StellaOps.Messaging.Transport.Postgres.csproj (73%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/Options/ValkeyTransportOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/StellaOps.Messaging.Transport.Valkey.csproj (75%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyAtomicTokenStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyCacheFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyCacheStore.cs (98%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyConnectionFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyEventStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyIdempotencyStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyMessageLease.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyMessageQueue.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyMessageQueueFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeySetStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeySortedIndex.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging.Transport.Valkey/ValkeyTransportPlugin.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IAtomicTokenStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IDistributedCache.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IEventStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IIdempotencyStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IMessageLease.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IMessageQueue.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IMessageQueueFactory.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/IRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/ISetStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Abstractions/ISortedIndex.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/DependencyInjection/MessagingServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Options/CacheOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Options/EventStreamOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Options/MessageQueueOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Options/MessagingPluginOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/CacheResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/EnqueueOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/EnqueueResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/EventStreamResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/IdempotencyResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/LeaseRequest.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/RateLimitResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/Results/TokenResult.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Messaging/StellaOps.Messaging.csproj (73%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/AspNetCoreEndpointDiscoveryProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/AspNetEndpointDescriptor.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/AspNetEndpointOverrideMerger.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/AspNetRouterRequestDispatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/DefaultAuthorizationClaimMapper.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/IAspNetEndpointDiscoveryProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/IAspNetRouterRequestDispatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/IAuthorizationClaimMapper.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/StellaOps.Microservice.AspNetCore.csproj (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/StellaRouterBridgeExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.AspNetCore/StellaRouterBridgeOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/DiagnosticDescriptors.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/EndpointInfo.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/Polyfills.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/SchemaGenerator.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/StellaEndpointGenerator.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice.SourceGen/StellaOps.Microservice.SourceGen.csproj (89%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/EndpointDiscoveryService.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/EndpointOverrideMerger.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/EndpointRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Endpoints/SchemaDiscoveryEndpoints.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/GeneratedEndpointDiscoveryProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/HeaderCollection.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IEndpointDiscoveryProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IEndpointRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IGeneratedEndpointProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IHeaderCollection.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IRequestDispatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IRouterConnectionManager.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/IStellaEndpoint.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/InflightRequestTracker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/MicroserviceHostedService.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/MicroserviceYamlConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/MicroserviceYamlLoader.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/PathMatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/RawRequestContext.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/RawResponse.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/ReflectionEndpointDiscoveryProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/RequestDispatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/RouterConnectionManager.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/RouterEndpointConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/StellaEndpointAttribute.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/StellaMicroserviceOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Microservice/StellaOps.Microservice.csproj rename src/{ => Router}/__Libraries/StellaOps.Microservice/Streaming/StreamingRequestBodyStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Streaming/StreamingResponseBodyStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/TypedEndpointAdapter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/ValidateSchemaAttribute.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/EndpointSchemaDefinition.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/IGeneratedSchemaProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/IRequestSchemaValidator.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/ISchemaRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/RequestSchemaValidator.cs (95%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/SchemaDirection.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/SchemaRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/SchemaValidationError.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/SchemaValidationException.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Microservice/Validation/ValidationProblemDetails.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/CompositeRequestDispatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/StellaRouterExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/StellaRouterIntegrationHelper.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/StellaRouterOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.AspNet/StellaRouterOptionsBase.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/IGlobalRoutingState.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/IMicroserviceTransport.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/IRegionProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/IRoutingPlugin.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/ITransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Abstractions/ITransportServer.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Enums/FrameType.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Enums/InstanceHealthStatus.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Enums/TransportType.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Frames/FrameConverter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Frames/RequestFrame.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Frames/ResponseFrame.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/CancelPayload.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/ClaimRequirement.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/ConnectionState.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/EndpointDescriptor.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/EndpointSchemaInfo.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/Frame.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/HeartbeatPayload.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/HelloPayload.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/InstanceDescriptor.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/PayloadLimits.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/RoutingContext.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/RoutingDecision.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/SchemaDefinition.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/ServiceOpenApiInfo.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/StreamDataPayload.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/Models/StreamingOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Common/PathMatcher.cs (100%) rename src/{Authority/StellaOps.Authority/StellaOps.Authority.Storage.InMemory/StellaOps.Authority.Storage.InMemory.csproj => Router/__Libraries/StellaOps.Router.Common/StellaOps.Router.Common.csproj} (55%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/IRouterConfigProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/RouterConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/RouterConfigOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/RouterConfigProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/RoutingOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/ServiceConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/StaticInstanceConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Config/StellaOps.Router.Config.csproj (71%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/AGENTS.md (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/ApplicationBuilderExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/AuthorityClaimsRefreshService.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/AuthorityConnectionOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/AuthorizationMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/AuthorizationServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/EffectiveClaimsStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/EndpointKey.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/HttpAuthorityClaimsProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/IAuthorityClaimsProvider.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Authorization/IEffectiveClaimsStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Configuration/HealthOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Configuration/RouterNodeConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Configuration/RoutingOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/DependencyInjection/RouterServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/GlobalUsings.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/ByteCountingStream.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/EndpointResolutionMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/GlobalErrorHandlerMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/PayloadLimitExceededException.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/PayloadLimitsMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/PayloadTracker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/RequestLoggingMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/RouterErrorWriter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/RoutingDecisionMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Middleware/TransportDispatchMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/ClaimSecurityMapper.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/IOpenApiDocumentGenerator.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/IRouterOpenApiDocumentCache.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/OpenApiAggregationOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/OpenApiDocumentGenerator.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/OpenApiEndpoints.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/OpenApi/RouterOpenApiDocumentCache.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/CircuitBreaker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/EnvironmentRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/InMemoryValkeyRateLimitStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/InstanceRateLimiter.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/LimitInheritanceResolver.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitConfig.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitDecision.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitMetrics.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitMiddleware.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitRouteMatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitRule.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitService.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/RateLimitServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RateLimit/ValkeyRateLimitStore.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/RouterHttpContextKeys.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Routing/DefaultRoutingPlugin.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Services/ConnectionManager.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Services/HealthMonitorService.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/Services/PingTracker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/State/InMemoryRoutingState.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Gateway/StellaOps.Router.Gateway.csproj (87%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryChannel.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryConnectionRegistry.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryTransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryTransportOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryTransportPlugin.cs rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/InMemoryTransportServer.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.InMemory/StellaOps.Router.Transport.InMemory.csproj (81%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/MessagingTransportServer.cs (98%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/Options/MessagingTransportOptions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/Protocol/CorrelationTracker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/Protocol/RpcRequestMessage.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/Protocol/RpcResponseMessage.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Messaging/StellaOps.Router.Transport.Messaging.csproj (82%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/RabbitMqFrameProtocol.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/RabbitMqTransportClient.cs (98%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/RabbitMqTransportOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Router.Transport.RabbitMq/RabbitMqTransportPlugin.cs rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/RabbitMqTransportServer.cs (98%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.RabbitMq/StellaOps.Router.Transport.RabbitMq.csproj (74%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/FrameProtocol.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/PendingRequestTracker.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/StellaOps.Router.Transport.Tcp.csproj (80%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/TcpConnection.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/TcpTransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/TcpTransportOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Router.Transport.Tcp/TcpTransportPlugin.cs rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tcp/TcpTransportServer.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/CertificateLoader.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/CertificateWatcher.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/StellaOps.Router.Transport.Tls.csproj (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/TlsConnection.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/TlsTransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/TlsTransportOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Router.Transport.Tls/TlsTransportPlugin.cs rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Tls/TlsTransportServer.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/PayloadTooLargeException.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/ServiceCollectionExtensions.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/StellaOps.Router.Transport.Udp.csproj (80%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/UdpFrameProtocol.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/UdpTransportClient.cs (100%) rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/UdpTransportOptions.cs (100%) create mode 100644 src/Router/__Libraries/StellaOps.Router.Transport.Udp/UdpTransportPlugin.cs rename src/{ => Router}/__Libraries/StellaOps.Router.Transport.Udp/UdpTransportServer.cs (100%) create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Authorization/AuthorizationMiddlewareTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Authorization/EffectiveClaimsStoreTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayOptionsValidatorTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Configuration/GatewayValueParserTests.cs rename src/{ => Router}/__Tests/StellaOps.Gateway.WebService.Tests/GatewayHealthTests.cs (97%) create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Integration/GatewayIntegrationTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Integration/MessagingTransportIntegrationTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Middleware/ClaimsPropagationMiddlewareTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Middleware/CorrelationIdMiddlewareTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Middleware/GatewayRoutesTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Middleware/IdentityHeaderPolicyMiddlewareTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/Middleware/TenantMiddlewareTests.cs create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj create mode 100644 src/Router/__Tests/StellaOps.Gateway.WebService.Tests/xunit.runner.json rename src/{__Libraries => Router}/__Tests/StellaOps.Messaging.Transport.Valkey.Tests/AtLeastOnceDeliveryTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Messaging.Transport.Valkey.Tests/Fixtures/ValkeyContainerFixture.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Messaging.Transport.Valkey.Tests/Fixtures/ValkeyIntegrationFactAttribute.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Messaging.Transport.Valkey.Tests/StellaOps.Messaging.Transport.Valkey.Tests.csproj (54%) rename src/{__Libraries => Router}/__Tests/StellaOps.Messaging.Transport.Valkey.Tests/ValkeyTransportComplianceTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.SourceGen.Tests/StellaEndpointGeneratorTests.cs (100%) create mode 100644 src/Router/__Tests/StellaOps.Microservice.SourceGen.Tests/StellaOps.Microservice.SourceGen.Tests.csproj rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/EndpointDiscoveryServiceTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/EndpointRegistryTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/HeaderCollectionTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/InflightRequestTrackerTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/RawRequestContextTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/RawResponseTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/RouterConnectionManagerTests.cs (97%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/StellaOps.Microservice.Tests.csproj (54%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/Validation/RequestSchemaValidatorTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/Validation/SchemaRegistryTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Microservice.Tests/Validation/ValidationProblemDetailsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/FrameConverterTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/MessageFramingRoundTripTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/PathMatcherTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/RoutingDeterminismTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/RoutingRulesEvaluationTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Common.Tests/StellaOps.Router.Common.Tests.csproj (58%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/ConfigChangedEventArgsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/ConfigValidationResultTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/RouterConfigOptionsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/RouterConfigProviderTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/RouterConfigTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/RoutingOptionsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/ServiceConfigTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/StaticInstanceConfigTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Config.Tests/StellaOps.Router.Config.Tests.csproj (58%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/ConnectionManagerIntegrationTests.cs (98%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/EndToEndRoutingTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/EndpointRegistryIntegrationTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/Fixtures/MicroserviceIntegrationFixture.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/Fixtures/TestEndpoints.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/MessageOrderingTests.cs (92%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/ParameterBindingTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/PathMatchingIntegrationTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/RequestDispatchIntegrationTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/ServiceRegistrationIntegrationTests.cs (98%) create mode 100644 src/Router/__Tests/StellaOps.Router.Integration.Tests/StellaOps.Router.Integration.Tests.csproj rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Integration.Tests/TransportIntegrationTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/BackpressureTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryChannelTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryConnectionRegistryTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryTransportComplianceTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryTransportOptionsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.InMemory.Tests/StellaOps.Router.Transport.InMemory.Tests.csproj (57%) create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Plugin.Tests/RouterTransportPluginLoaderTests.cs create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Plugin.Tests/StellaOps.Router.Transport.Plugin.Tests.csproj create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Plugin.Tests/TransportPluginRegistrationTests.cs rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/Fixtures/RabbitMqContainerFixture.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/Fixtures/RabbitMqIntegrationFactAttribute.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqFrameProtocolTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqIntegrationTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqTransportClientTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqTransportComplianceTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqTransportOptionsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/RabbitMqTransportServerTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.RabbitMq.Tests/StellaOps.Router.Transport.RabbitMq.Tests.csproj (53%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tcp.Tests/ConnectionFailureTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tcp.Tests/FrameFuzzTests.cs (99%) create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Tcp.Tests/StellaOps.Router.Transport.Tcp.Tests.csproj rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tcp.Tests/TcpTransportComplianceTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tcp.Tests/TcpTransportTests.cs (99%) create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Tls.Tests/StellaOps.Router.Transport.Tls.Tests.csproj rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tls.Tests/TlsTransportComplianceTests.cs (98%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Tls.Tests/TlsTransportTests.cs (99%) create mode 100644 src/Router/__Tests/StellaOps.Router.Transport.Udp.Tests/StellaOps.Router.Transport.Udp.Tests.csproj rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Udp.Tests/UdpFrameProtocolTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Udp.Tests/UdpTransportClientTests.cs (99%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Udp.Tests/UdpTransportOptionsTests.cs (100%) rename src/{__Libraries => Router}/__Tests/StellaOps.Router.Transport.Udp.Tests/UdpTransportServerTests.cs (99%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Messaging.Testing/Builders/TestMessageBuilder.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Messaging.Testing/Fixtures/InMemoryMessagingFixture.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Messaging.Testing/Fixtures/PostgresQueueFixture.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Messaging.Testing/Fixtures/ValkeyFixture.cs (98%) create mode 100644 src/Router/__Tests/__Libraries/StellaOps.Messaging.Testing/StellaOps.Messaging.Testing.csproj rename src/{ => Router}/__Tests/__Libraries/StellaOps.Router.Testing/Factories/TestFrameFactory.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Router.Testing/Fixtures/RouterTestFixture.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Router.Testing/Mocks/MockConnectionState.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Router.Testing/Mocks/RecordingLogger.cs (100%) rename src/{ => Router}/__Tests/__Libraries/StellaOps.Router.Testing/StellaOps.Router.Testing.csproj (61%) create mode 100644 src/Router/examples/Examples.Billing.Microservice/Endpoints/CreateInvoiceEndpoint.cs create mode 100644 src/Router/examples/Examples.Billing.Microservice/Endpoints/GetInvoiceEndpoint.cs create mode 100644 src/Router/examples/Examples.Billing.Microservice/Endpoints/UploadAttachmentEndpoint.cs create mode 100644 src/Router/examples/Examples.Billing.Microservice/Examples.Billing.Microservice.csproj create mode 100644 src/Router/examples/Examples.Billing.Microservice/Program.cs create mode 100644 src/Router/examples/Examples.Billing.Microservice/microservice.yaml create mode 100644 src/Router/examples/Examples.Gateway/Examples.Gateway.csproj create mode 100644 src/Router/examples/Examples.Gateway/Program.cs create mode 100644 src/Router/examples/Examples.Gateway/Properties/launchSettings.json create mode 100644 src/Router/examples/Examples.Gateway/appsettings.json create mode 100644 src/Router/examples/Examples.Gateway/router.yaml create mode 100644 src/Router/examples/Examples.Inventory.Microservice/Endpoints/GetItemEndpoint.cs create mode 100644 src/Router/examples/Examples.Inventory.Microservice/Endpoints/ListItemsEndpoint.cs create mode 100644 src/Router/examples/Examples.Inventory.Microservice/Examples.Inventory.Microservice.csproj create mode 100644 src/Router/examples/Examples.Inventory.Microservice/Program.cs create mode 100644 src/Router/examples/Examples.MultiTransport.Gateway/Examples.MultiTransport.Gateway.csproj create mode 100644 src/Router/examples/Examples.MultiTransport.Gateway/Program.cs create mode 100644 src/Router/examples/Examples.MultiTransport.Gateway/Properties/launchSettings.json create mode 100644 src/Router/examples/Examples.MultiTransport.Gateway/appsettings.json create mode 100644 src/Router/examples/Examples.MultiTransport.Gateway/router.yaml create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/BroadcastNotificationEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/GetNotificationsEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/MarkNotificationsReadEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/SendNotificationEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/SendTemplatedNotificationEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/SubscribeNotificationsEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Endpoints/UpdatePreferencesEndpoint.cs create mode 100644 src/Router/examples/Examples.NotificationService/Examples.NotificationService.csproj create mode 100644 src/Router/examples/Examples.NotificationService/Models/Notification.cs create mode 100644 src/Router/examples/Examples.NotificationService/Program.cs create mode 100644 src/Router/examples/Examples.NotificationService/microservice.yaml create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/CancelOrderEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/CreateOrderEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/ExportOrdersEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/GetOrderEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/ListOrdersEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/OrderEventsEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/UpdateOrderStatusEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Endpoints/UploadOrderDocumentEndpoint.cs create mode 100644 src/Router/examples/Examples.OrderService/Examples.OrderService.csproj create mode 100644 src/Router/examples/Examples.OrderService/Models/Order.cs create mode 100644 src/Router/examples/Examples.OrderService/Program.cs create mode 100644 src/Router/examples/Examples.OrderService/microservice.yaml delete mode 100644 src/SbomService/StellaOps.SbomService.Storage.Postgres.Tests/StellaOps.SbomService.Storage.Postgres.Tests.csproj delete mode 100644 src/SbomService/StellaOps.SbomService.Storage.Postgres/StellaOps.SbomService.Storage.Postgres.csproj create mode 100644 src/SbomService/StellaOps.SbomService/Models/ReplayVerificationModels.cs create mode 100644 src/SbomService/StellaOps.SbomService/Properties/launchSettings.json create mode 100644 src/SbomService/StellaOps.SbomService/Repositories/ISbomLineageEdgeRepository.cs create mode 100644 src/SbomService/StellaOps.SbomService/Repositories/InMemorySbomLineageEdgeRepository.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/ILineageCompareCache.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/ILineageCompareService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/IReplayHashService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/IReplayVerificationService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/ISbomLineageGraphService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/InMemoryLineageCompareCache.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/LineageCompareService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/LineageHoverCache.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/ReplayHashService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/ReplayVerificationService.cs create mode 100644 src/SbomService/StellaOps.SbomService/Services/SbomLineageGraphService.cs create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/EfCore/Context/SbomServiceDbContext.cs rename src/SbomService/{StellaOps.SbomService.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.SbomService.Persistence/Extensions/SbomServicePersistenceExtensions.cs} (67%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresCatalogRepository.cs (99%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresComponentLookupRepository.cs (98%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresEntrypointRepository.cs (98%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresOrchestratorControlRepository.cs (98%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresOrchestratorRepository.cs (98%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/Repositories/PostgresProjectionRepository.cs (98%) create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/Postgres/Repositories/PostgresSbomLineageEdgeRepository.cs create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/Postgres/Repositories/PostgresSbomVerdictLinkRepository.cs rename src/SbomService/{StellaOps.SbomService.Storage.Postgres => __Libraries/StellaOps.SbomService.Persistence/Postgres}/SbomServiceDataSource.cs (95%) create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/Repositories/ISbomLineageEdgeRepository.cs create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/Repositories/ISbomVerdictLinkRepository.cs create mode 100644 src/SbomService/__Libraries/StellaOps.SbomService.Persistence/StellaOps.SbomService.Persistence.csproj rename src/SbomService/{StellaOps.SbomService.Storage.Postgres.Tests => __Tests/StellaOps.SbomService.Persistence.Tests}/PostgresEntrypointRepositoryTests.cs (96%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres.Tests => __Tests/StellaOps.SbomService.Persistence.Tests}/PostgresOrchestratorControlRepositoryTests.cs (94%) rename src/SbomService/{StellaOps.SbomService.Storage.Postgres.Tests => __Tests/StellaOps.SbomService.Persistence.Tests}/SbomServicePostgresFixture.cs (89%) create mode 100644 src/SbomService/__Tests/StellaOps.SbomService.Persistence.Tests/StellaOps.SbomService.Persistence.Tests.csproj delete mode 100644 src/Scanner/StellaOps.Scanner.Node.Phase22.slnf delete mode 100644 src/Scanner/StellaOps.Scanner.Node.Phase22.slnx delete mode 100644 src/Scanner/StellaOps.Scanner.Node.slnf create mode 100644 src/Scanner/StellaOps.Scanner.WebService/Properties/launchSettings.json create mode 100644 src/Scanner/StellaOps.Scanner.WebService/Services/IOciAttestationPublisher.cs create mode 100644 src/Scanner/StellaOps.Scanner.WebService/Services/NullOciAttestationPublisher.cs create mode 100644 src/Scanner/StellaOps.Scanner.WebService/Services/OciAttestationPublisher.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/Internal/Crypto/DotNetCryptoExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/Internal/Crypto/JavaCryptoExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/Internal/Crypto/NodeCryptoExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.CallGraph/Extraction/GuardDetector.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Emit/Cbom/CbomAggregationService.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Emit/Cbom/CbomSerializer.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Emit/Cbom/CryptoProperties.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Emit/Cbom/ICryptoAssetExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxCbomWriter.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Reachability/Runtime/EbpfSignalMerger.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/CliSourceConfig.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/DockerSourceConfig.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/GitSourceConfig.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/ISourceConfigValidator.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/SourceConfigValidator.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Configuration/ZastavaSourceConfig.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Contracts/SourceContracts.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Domain/SbomSource.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Domain/SbomSourceEnums.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Domain/SbomSourceRun.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Persistence/ISbomSourceRepository.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Persistence/SbomSourceRepository.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Persistence/SbomSourceRunRepository.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Persistence/ScannerSourcesDataSource.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Services/ISbomSourceService.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Services/ISourceConnectionTester.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Services/SbomSourceService.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/Services/SourceConnectionTester.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Sources/StellaOps.Scanner.Sources.csproj create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Storage.Oci/IOciAncestryExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Storage.Oci/OciAncestryExtractor.cs create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations/001_initial_schema.sql create mode 100644 src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations/_archived/pre_1.0/README.md create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Cbom/CbomSerializerTests.cs create mode 100644 src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Cbom/CbomTests.cs create mode 100644 src/Scheduler/StellaOps.Scheduler.WebService/Properties/launchSettings.json create mode 100644 src/Scheduler/__Libraries/StellaOps.Scheduler.Persistence/EfCore/Context/SchedulerDbContext.cs rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/ServiceCollectionExtensions.cs => StellaOps.Scheduler.Persistence/Extensions/SchedulerPersistenceExtensions.cs} (83%) create mode 100644 src/Scheduler/__Libraries/StellaOps.Scheduler.Persistence/Migrations/001_initial_schema.sql rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/001_initial_schema.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/002_graph_jobs.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/003_runs_policy.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/010_generated_columns_runs.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/011_enable_rls.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/012_partition_audit.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres/Migrations => StellaOps.Scheduler.Persistence/Migrations/_archived/pre_1.0}/012b_migrate_audit_data.sql (100%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/CanonicalJsonSerializer.cs (92%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/FailureSignatureEntity.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/JobEntity.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/JobHistoryEntity.cs (97%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/LockEntity.cs (93%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/MetricsEntity.cs (96%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/TriggerEntity.cs (97%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Models/WorkerEntity.cs (96%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/DistributedLockRepository.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/FailureSignatureRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/GraphJobRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IDistributedLockRepository.cs (91%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IFailureSignatureRepository.cs (97%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IGraphJobRepository.cs (96%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IImpactSnapshotRepository.cs (81%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IJobHistoryRepository.cs (94%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IJobRepository.cs (95%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IMetricsRepository.cs (91%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IPolicyRunJobRepository.cs (94%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IRunRepository.cs (90%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IScheduleRepository.cs (89%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/ITriggerRepository.cs (94%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/IWorkerRepository.cs (93%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/ImpactSnapshotRepository.cs (96%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/JobHistoryRepository.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/JobRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/MetricsRepository.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/PolicyRunJobRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/RunQueryOptions.cs (87%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/RunRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/RunSummaryService.cs (97%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/ScheduleQueryOptions.cs (74%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/ScheduleRepository.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/TriggerRepository.cs (99%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/Repositories/WorkerRepository.cs (98%) rename src/Scheduler/__Libraries/{StellaOps.Scheduler.Storage.Postgres => StellaOps.Scheduler.Persistence/Postgres}/SchedulerDataSource.cs (95%) create mode 100644 src/Scheduler/__Libraries/StellaOps.Scheduler.Persistence/StellaOps.Scheduler.Persistence.csproj delete mode 100644 src/Scheduler/__Libraries/StellaOps.Scheduler.Storage.Postgres/StellaOps.Scheduler.Storage.Postgres.csproj rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/DistributedLockRepositoryTests.cs (98%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/GraphJobRepositoryTests.cs (96%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/JobIdempotencyTests.cs (98%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/SchedulerMigrationTests.cs (99%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/SchedulerPostgresFixture.cs (95%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/SchedulerQueryDeterminismTests.cs (98%) create mode 100644 src/Scheduler/__Tests/StellaOps.Scheduler.Persistence.Tests/StellaOps.Scheduler.Persistence.Tests.csproj rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/TriggerRepositoryTests.cs (97%) rename src/Scheduler/__Tests/{StellaOps.Scheduler.Storage.Postgres.Tests => StellaOps.Scheduler.Persistence.Tests}/WorkerRepositoryTests.cs (96%) delete mode 100644 src/Scheduler/__Tests/StellaOps.Scheduler.Storage.Postgres.Tests/StellaOps.Scheduler.Storage.Postgres.Tests.csproj delete mode 100644 src/Signals/StellaOps.Signals.Storage.Postgres.Tests/StellaOps.Signals.Storage.Postgres.Tests.csproj delete mode 100644 src/Signals/StellaOps.Signals.Storage.Postgres/AGENTS.md delete mode 100644 src/Signals/StellaOps.Signals.Storage.Postgres/StellaOps.Signals.Storage.Postgres.csproj create mode 100644 src/Signals/StellaOps.Signals/Properties/launchSettings.json create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Probes/AirGapProbeLoader.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Probes/CoreProbeLoader.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Probes/IEbpfProbeLoader.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Schema/RuntimeCallEvent.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Services/IRuntimeSignalCollector.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/Services/RuntimeSignalCollector.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Ebpf/StellaOps.Signals.Ebpf.csproj create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Persistence/EfCore/Context/SignalsDbContext.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Persistence/Extensions/SignalsPersistenceExtensions.cs create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Persistence/Migrations/001_initial_schema.sql create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Persistence/Migrations/_archived/pre_1.0/README.md rename src/Signals/{StellaOps.Signals.Storage.Postgres/Migrations => __Libraries/StellaOps.Signals.Persistence/Migrations/_archived/pre_1.0}/V0000_001__extensions.sql (100%) rename src/Signals/{StellaOps.Signals.Storage.Postgres/Migrations => __Libraries/StellaOps.Signals.Persistence/Migrations/_archived/pre_1.0}/V1102_001__unknowns_scoring_schema.sql (100%) rename src/Signals/{StellaOps.Signals.Storage.Postgres/Migrations => __Libraries/StellaOps.Signals.Persistence/Migrations/_archived/pre_1.0}/V1105_001__deploy_refs_graph_metrics.sql (100%) rename src/Signals/{StellaOps.Signals.Storage.Postgres/Migrations => __Libraries/StellaOps.Signals.Persistence/Migrations/_archived/pre_1.0}/V3102_001__callgraph_relational_tables.sql (100%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/ICallGraphQueryRepository.cs (97%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresCallGraphProjectionRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresCallGraphQueryRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresCallgraphRepository.cs (98%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresDeploymentRefsRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresGraphMetricsRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresReachabilityFactRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresReachabilityStoreRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/Repositories/PostgresUnknownsRepository.cs (99%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/ServiceCollectionExtensions.cs (97%) rename src/Signals/{StellaOps.Signals.Storage.Postgres => __Libraries/StellaOps.Signals.Persistence/Postgres}/SignalsDataSource.cs (96%) create mode 100644 src/Signals/__Libraries/StellaOps.Signals.Persistence/StellaOps.Signals.Persistence.csproj create mode 100644 src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/EbpfSignalMergerTests.cs create mode 100644 src/Signals/__Tests/StellaOps.Signals.Ebpf.Tests/RuntimeSignalCollectorTests.cs rename src/{__Tests/StellaOps.Router.Common.Tests/StellaOps.Router.Common.Tests.csproj => Signals/__Tests/StellaOps.Signals.Ebpf.Tests/StellaOps.Signals.Ebpf.Tests.csproj} (51%) rename src/Signals/{StellaOps.Signals.Storage.Postgres.Tests => __Tests/StellaOps.Signals.Persistence.Tests}/CallGraphProjectionIntegrationTests.cs (97%) rename src/Signals/{StellaOps.Signals.Storage.Postgres.Tests => __Tests/StellaOps.Signals.Persistence.Tests}/CallGraphSyncServiceTests.cs (96%) rename src/Signals/{StellaOps.Signals.Storage.Postgres.Tests => __Tests/StellaOps.Signals.Persistence.Tests}/PostgresCallgraphRepositoryTests.cs (97%) rename src/Signals/{StellaOps.Signals.Storage.Postgres.Tests => __Tests/StellaOps.Signals.Persistence.Tests}/SignalsPostgresFixture.cs (89%) create mode 100644 src/Signals/__Tests/StellaOps.Signals.Persistence.Tests/StellaOps.Signals.Persistence.Tests.csproj create mode 100644 src/Signer/StellaOps.Signer/StellaOps.Signer.Core/Predicates/DeltaPredicateSchemas.cs create mode 100644 src/Signer/StellaOps.Signer/StellaOps.Signer.WebService/Properties/launchSettings.json delete mode 100644 src/Signer/StellaOps.Signer/StellaOps.Signer.sln create mode 100644 src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations/001_initial_schema.sql rename src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations/{ => _archived/pre_1.0}/20251214000001_AddKeyManagementSchema.sql (100%) create mode 100644 src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations/_archived/pre_1.0/README.md create mode 100644 src/SmRemote/StellaOps.SmRemote.Service/Properties/launchSettings.json create mode 100644 src/SmRemote/StellaOps.SmRemote.sln delete mode 100644 src/StellaOps.AdvisoryAI.sln delete mode 100644 src/StellaOps.AirGap.sln delete mode 100644 src/StellaOps.Aoc.sln delete mode 100644 src/StellaOps.Attestor.sln delete mode 100644 src/StellaOps.Authority.sln delete mode 100644 src/StellaOps.Bench.sln delete mode 100644 src/StellaOps.BinaryIndex.sln delete mode 100644 src/StellaOps.Cartographer.sln delete mode 100644 src/StellaOps.Cli.sln delete mode 100644 src/StellaOps.Concelier.sln delete mode 100644 src/StellaOps.Events.Provenance.Tests/StellaOps.Events.Provenance.Tests.csproj delete mode 100644 src/StellaOps.EvidenceLocker.sln delete mode 100644 src/StellaOps.Excititor.sln delete mode 100644 src/StellaOps.ExportCenter.sln delete mode 100644 src/StellaOps.Gateway.sln delete mode 100644 src/StellaOps.Graph.sln delete mode 100644 src/StellaOps.Infrastructure.sln delete mode 100644 src/StellaOps.IssuerDirectory.sln delete mode 100644 src/StellaOps.Notify.sln delete mode 100644 src/StellaOps.Orchestrator.sln delete mode 100644 src/StellaOps.Policy.sln delete mode 100644 src/StellaOps.Replay.sln delete mode 100644 src/StellaOps.SbomService.sln delete mode 100644 src/StellaOps.Scanner.sln delete mode 100644 src/StellaOps.Scheduler.sln delete mode 100644 src/StellaOps.Signals.sln delete mode 100644 src/StellaOps.Signer.sln delete mode 100644 src/StellaOps.TaskRunner.sln delete mode 100644 src/StellaOps.Telemetry.sln delete mode 100644 src/StellaOps.Tests.sln delete mode 100644 src/StellaOps.Tests.slnx delete mode 100644 src/StellaOps.VexHub.sln delete mode 100644 src/StellaOps.VexLens.sln delete mode 100644 src/StellaOps.VulnExplorer.sln delete mode 100644 src/StellaOps.Zastava.sln create mode 100644 src/Symbols/StellaOps.Symbols.Server/Properties/launchSettings.json delete mode 100644 src/TaskRunner/StellaOps.TaskRunner.Storage.Postgres.Tests/StellaOps.TaskRunner.Storage.Postgres.Tests.csproj delete mode 100644 src/TaskRunner/StellaOps.TaskRunner.Storage.Postgres/StellaOps.TaskRunner.Storage.Postgres.csproj delete mode 100644 src/TaskRunner/StellaOps.TaskRunner/StellaOps.TaskRunner.sln create mode 100644 src/TaskRunner/__Libraries/StellaOps.TaskRunner.Persistence/EfCore/Context/TaskRunnerDbContext.cs rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres/ServiceCollectionExtensions.cs => __Libraries/StellaOps.TaskRunner.Persistence/Extensions/TaskRunnerPersistenceExtensions.cs} (79%) rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres => __Libraries/StellaOps.TaskRunner.Persistence/Postgres}/Repositories/PostgresPackRunApprovalStore.cs (99%) rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres => __Libraries/StellaOps.TaskRunner.Persistence/Postgres}/Repositories/PostgresPackRunEvidenceStore.cs (99%) rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres => __Libraries/StellaOps.TaskRunner.Persistence/Postgres}/Repositories/PostgresPackRunLogStore.cs (98%) rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres => __Libraries/StellaOps.TaskRunner.Persistence/Postgres}/Repositories/PostgresPackRunStateStore.cs (99%) rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres => __Libraries/StellaOps.TaskRunner.Persistence/Postgres}/TaskRunnerDataSource.cs (95%) create mode 100644 src/TaskRunner/__Libraries/StellaOps.TaskRunner.Persistence/StellaOps.TaskRunner.Persistence.csproj rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres.Tests => __Tests/StellaOps.TaskRunner.Persistence.Tests}/PostgresPackRunStateStoreTests.cs (95%) create mode 100644 src/TaskRunner/__Tests/StellaOps.TaskRunner.Persistence.Tests/StellaOps.TaskRunner.Persistence.Tests.csproj rename src/TaskRunner/{StellaOps.TaskRunner.Storage.Postgres.Tests => __Tests/StellaOps.TaskRunner.Persistence.Tests}/TaskRunnerPostgresFixture.cs (90%) create mode 100644 src/Telemetry/StellaOps.Telemetry.Analyzers/StellaOps.Telemetry.Analyzers.Tests/TestCategories.cs delete mode 100644 src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core.Tests/Directory.Build.props delete mode 100644 src/Telemetry/StellaOps.Telemetry.Core/StellaOps.Telemetry.Core.Tests/Directory.Build.targets create mode 100644 src/Telemetry/StellaOps.Telemetry.sln delete mode 100644 src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.sln create mode 100644 src/Tools/StellaOps.Tools.sln create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence.EfCore/Context/UnknownsDbContext.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence.EfCore/Extensions/UnknownsPersistenceExtensions.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence.EfCore/README.md create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence.EfCore/Repositories/UnknownEfRepository.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence.EfCore/StellaOps.Unknowns.Persistence.EfCore.csproj create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/EfCore/CompiledModels/.gitkeep create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/EfCore/Context/UnknownsDbContext.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/EfCore/Entities/.gitkeep create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/EfCore/Mappings/.gitkeep create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/EfCore/Repositories/UnknownEfRepository.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/Extensions/UnknownsPersistenceExtensions.cs create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/InMemory/Repositories/.gitkeep create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/Migrations/001_initial_schema.sql rename src/Unknowns/__Libraries/{StellaOps.Unknowns.Storage.Postgres/Migrations => StellaOps.Unknowns.Persistence/Migrations/_archived/pre_1.0}/001_initial_schema.sql (100%) rename src/Unknowns/__Libraries/{StellaOps.Unknowns.Storage.Postgres/Migrations => StellaOps.Unknowns.Persistence/Migrations/_archived/pre_1.0}/002_scoring_extension.sql (100%) rename src/Unknowns/__Libraries/{StellaOps.Unknowns.Storage.Postgres/Persistence => StellaOps.Unknowns.Persistence/Postgres}/PostgresUnknownPersister.cs (98%) rename src/Unknowns/__Libraries/{StellaOps.Unknowns.Storage.Postgres => StellaOps.Unknowns.Persistence/Postgres}/Repositories/PostgresUnknownRepository.cs (97%) create mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Persistence/StellaOps.Unknowns.Persistence.csproj delete mode 100644 src/Unknowns/__Libraries/StellaOps.Unknowns.Storage.Postgres/StellaOps.Unknowns.Storage.Postgres.csproj rename src/Unknowns/__Tests/{StellaOps.Unknowns.Storage.Postgres.Tests => StellaOps.Unknowns.Persistence.Tests}/PostgresUnknownRepositoryTests.cs (89%) create mode 100644 src/Unknowns/__Tests/StellaOps.Unknowns.Persistence.Tests/StellaOps.Unknowns.Persistence.Tests.csproj delete mode 100644 src/Unknowns/__Tests/StellaOps.Unknowns.Storage.Postgres.Tests/StellaOps.Unknowns.Storage.Postgres.Tests.csproj create mode 100644 src/VexHub/StellaOps.VexHub.WebService/Properties/launchSettings.json create mode 100644 src/VexHub/__Libraries/StellaOps.VexHub.Persistence/EfCore/Context/VexHubDbContext.cs create mode 100644 src/VexHub/__Libraries/StellaOps.VexHub.Persistence/Extensions/VexHubPersistenceExtensions.cs rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence}/Migrations/001_initial_schema.sql (100%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Models/VexConflictEntity.cs (92%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Models/VexIngestionJobEntity.cs (92%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Models/VexProvenanceEntity.cs (91%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Models/VexSourceEntity.cs (93%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Models/VexStatementEntity.cs (95%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Repositories/PostgresVexProvenanceRepository.cs (97%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/Repositories/PostgresVexStatementRepository.cs (99%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres => StellaOps.VexHub.Persistence/Postgres}/VexHubDataSource.cs (96%) rename src/VexHub/__Libraries/{StellaOps.VexHub.Storage.Postgres/StellaOps.VexHub.Storage.Postgres.csproj => StellaOps.VexHub.Persistence/StellaOps.VexHub.Persistence.csproj} (52%) delete mode 100644 src/VexHub/__Libraries/StellaOps.VexHub.Storage.Postgres/Extensions/VexHubPostgresServiceCollectionExtensions.cs delete mode 100644 src/VexHub/__Tests/StellaOps.VexHub.Storage.Postgres.Tests/StellaOps.VexHub.Storage.Postgres.Tests.csproj create mode 100644 src/VexLens/StellaOps.VexLens.Persistence/Migrations/001_consensus_projections.sql create mode 100644 src/VexLens/StellaOps.VexLens.Persistence/Postgres/PostgresConsensusProjectionStore.cs create mode 100644 src/VexLens/StellaOps.VexLens.Persistence/StellaOps.VexLens.Persistence.csproj create mode 100644 src/VexLens/StellaOps.VexLens.sln create mode 100644 src/VexLens/StellaOps.VexLens/Services/VexDeltaComputeService.cs create mode 100644 src/VexLens/StellaOps.VexLens/Storage/DualWriteConsensusProjectionStore.cs create mode 100644 src/VexLens/StellaOps.VexLens/Storage/PostgresConsensusProjectionStoreProxy.cs create mode 100644 src/VulnExplorer/StellaOps.VulnExplorer.Api/Properties/launchSettings.json create mode 100644 src/VulnExplorer/StellaOps.VulnExplorer.WebService/Contracts/EvidenceSubgraphContracts.cs create mode 100644 src/VulnExplorer/StellaOps.VulnExplorer.sln create mode 100644 src/Web/StellaOps.Web/docs/ACCESSIBILITY_AUDIT_BINARY_RESOLUTION.md create mode 100644 src/Web/StellaOps.Web/e2e/binary-resolution.e2e.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/api/binary-resolution.client.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/api/binary-resolution.models.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/api/reachgraph.models.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/models/proof-spine.model.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/navigation/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/navigation/navigation.config.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/navigation/navigation.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/navigation/navigation.types.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/audit-pack.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/theme.service.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/theme.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/toast.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/view-preference.service.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/services/view-preference.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/findings/__tests__/findings-navigation.e2e.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/findings/container/findings-container.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/home/home-dashboard.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/home/home-dashboard.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/home/home-dashboard.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/attestation-links/attestation-links.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/compare-panel/compare-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/export-dialog/export-dialog.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/keyboard-shortcuts-help/keyboard-shortcuts-help.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-compare-panel/lineage-compare-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-compare/lineage-compare.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-component-diff/lineage-component-diff.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-controls/lineage-controls.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-detail-panel/lineage-detail-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-edge/lineage-edge.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-export-buttons/lineage-export-buttons.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-export-dialog/lineage-export-dialog.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-graph-container/lineage-graph-container.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-graph/lineage-graph.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-hover-card/lineage-hover-card.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-minimap/lineage-minimap.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-mobile-compare/lineage-mobile-compare.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-node/lineage-node.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-provenance-chips/lineage-provenance-chips.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-provenance-compare/lineage-provenance-compare.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-sbom-diff/lineage-sbom-diff.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-timeline-slider/lineage-timeline-slider.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-vex-delta/lineage-vex-delta.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-vex-diff/lineage-vex-diff.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/lineage-why-safe-panel/lineage-why-safe-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/reachability-diff-view/reachability-diff-view.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/replay-hash-display/replay-hash-display.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/timeline-slider/timeline-slider.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/vex-diff-view/vex-diff-view.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/components/why-safe-panel/why-safe-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/directives/lineage-accessibility.directive.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/directives/lineage-graph-highlight.directive.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/directives/lineage-keyboard-shortcuts.directive.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/directives/lineage-keyboard.directive.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/lineage.routes.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/models/lineage.models.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/routing/lineage-compare-routing.guard.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/services/lineage-export.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/services/lineage-graph.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/styles/lineage-accessibility.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/lineage/styles/lineage-mobile.styles.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/evidence-graph/evidence-graph.component.html create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/evidence-graph/evidence-graph.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/evidence-graph/evidence-graph.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/policy-breadcrumb/policy-breadcrumb.component.html create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/policy-breadcrumb/policy-breadcrumb.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/policy-breadcrumb/policy-breadcrumb.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-actions/verdict-actions.component.html create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-actions/verdict-actions.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-actions/verdict-actions.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-detail-panel/verdict-detail-panel.component.html create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-detail-panel/verdict-detail-panel.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/components/verdict-detail-panel/verdict-detail-panel.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/models/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/models/verdict.models.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/services/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/verdicts/services/verdict.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/citation-link/citation-link.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/evidence-subgraph.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/evidence-subgraph.stories.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/evidence-subgraph/evidence-subgraph.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/evidence-tree/evidence-tree.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/triage-card/triage-card.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/triage-filters/triage-filters.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/components/verdict-explanation/verdict-explanation.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/models/evidence-subgraph.models.ts create mode 100644 src/Web/StellaOps.Web/src/app/features/vuln-explorer/services/evidence-subgraph.service.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/accordion/accordion.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/alert/alert.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/attestation-viewer/attestation-viewer.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/audit-pack/export-audit-pack-button.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/audit-pack/export-audit-pack-button.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/audit-pack/export-audit-pack-dialog.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/audit-pack/export-audit-pack-dialog.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/audit-pack/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/avatar/avatar.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/badge/badge.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/breadcrumb/breadcrumb.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/button/button.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/card/card.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/command-palette/command-palette.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/command-palette/command-palette.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/confirm-dialog/confirm-dialog.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/copy-attestation/copy-attestation-button.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/copy-attestation/copy-attestation-button.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/copy-button/copy-button.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/data-table/data-table.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/divider/divider.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/dropdown/dropdown.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/empty-state/empty-state.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/evidence-drawer.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/evidence-drawer/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/findings-view-toggle/findings-view-toggle.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/findings-view-toggle/findings-view-toggle.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/function-diff/function-diff.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/keyboard-shortcuts/keyboard-shortcuts.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/loading/loading.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/modal/modal.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/navigation-menu/navigation-menu.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/navigation-menu/navigation-menu.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/pagination/pagination.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/progress-bar/progress-bar.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/__tests__/proof-spine.e2e.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/chain-integrity-badge.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/proof-badges-row.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/proof-segment.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/proof-spine.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/proof-spine.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/proof-spine/segment-detail-modal.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/resolution-chip/resolution-chip.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/resolution-chip/resolution-chip.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/search-input/search-input.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/skeleton/skeleton.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/smart-diff-badge/smart-diff-badge.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/smart-diff-badge/smart-diff-badge.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/stat-card/stat-card.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/tabs/tabs.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/theme-toggle/theme-toggle.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/toast/toast-container.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/tooltip/tooltip.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/ui/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/user-menu/user-menu.component.scss create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/user-menu/user-menu.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-chip/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-chip/vex-trust-chip.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-chip/vex-trust-chip.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-popover/index.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-popover/vex-trust-popover.component.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/components/vex-trust-popover/vex-trust-popover.component.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/pipes/format.pipes.ts create mode 100644 src/Web/StellaOps.Web/src/app/shared/pipes/truncate.pipe.ts create mode 100644 src/Web/StellaOps.Web/src/stories/trust/vex-trust-chip.stories.ts create mode 100644 src/Web/StellaOps.Web/src/styles/_forms.scss create mode 100644 src/Web/StellaOps.Web/src/styles/_interactions.scss create mode 100644 src/Web/StellaOps.Web/src/styles/tokens/_colors.scss create mode 100644 src/Zastava/StellaOps.Zastava.Observer/Probes/EbpfProbeManager.cs create mode 100644 src/Zastava/StellaOps.Zastava.Webhook/Properties/launchSettings.json create mode 100644 src/__Libraries/StellaOps.AuditPack/Services/AuditPackExportService.cs create mode 100644 src/__Libraries/StellaOps.AuditPack/Services/ReplayAttestationService.cs create mode 100644 src/__Libraries/StellaOps.AuditPack/Services/ReplayTelemetry.cs create mode 100644 src/__Libraries/StellaOps.AuditPack/Services/VerdictReplayPredicate.cs create mode 100644 src/__Libraries/StellaOps.Evidence.Persistence/EfCore/Context/EvidenceDbContext.cs create mode 100644 src/__Libraries/StellaOps.Evidence.Persistence/Extensions/EvidencePersistenceExtensions.cs rename src/__Libraries/{StellaOps.Evidence.Storage.Postgres => StellaOps.Evidence.Persistence}/Migrations/001_initial_schema.sql (100%) rename src/__Libraries/{StellaOps.Evidence.Storage.Postgres => StellaOps.Evidence.Persistence/Postgres}/EvidenceDataSource.cs (95%) rename src/__Libraries/{StellaOps.Evidence.Storage.Postgres => StellaOps.Evidence.Persistence/Postgres}/PostgresEvidenceStore.cs (99%) rename src/__Libraries/{StellaOps.Evidence.Storage.Postgres => StellaOps.Evidence.Persistence/Postgres}/PostgresEvidenceStoreFactory.cs (96%) create mode 100644 src/__Libraries/StellaOps.Evidence.Persistence/StellaOps.Evidence.Persistence.csproj delete mode 100644 src/__Libraries/StellaOps.Evidence.Storage.Postgres/ServiceCollectionExtensions.cs delete mode 100644 src/__Libraries/StellaOps.Evidence.Storage.Postgres/StellaOps.Evidence.Storage.Postgres.csproj create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Context/StellaOpsDbContextBase.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Extensions/DbContextServiceExtensions.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Interceptors/TenantConnectionInterceptor.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/StellaOps.Infrastructure.EfCore.csproj create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Tenancy/AsyncLocalTenantContextAccessor.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Tenancy/ITenantContextAccessor.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.EfCore/Tenancy/SystemTenantContextAccessor.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationDependency.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationTelemetry.cs create mode 100644 src/__Libraries/StellaOps.Infrastructure.Postgres/Migrations/MigrationValidator.cs delete mode 100644 src/__Libraries/StellaOps.Messaging/Plugins/IMessagingTransportPlugin.cs delete mode 100644 src/__Libraries/StellaOps.Messaging/Plugins/MessagingPluginLoader.cs delete mode 100644 src/__Libraries/StellaOps.Messaging/Plugins/MessagingTransportRegistrationContext.cs create mode 100644 src/__Libraries/StellaOps.Plugin/Manifest/PluginManifest.cs create mode 100644 src/__Libraries/StellaOps.Plugin/Manifest/PluginManifestLoader.cs create mode 100644 src/__Libraries/StellaOps.Plugin/Manifest/PluginRegistry.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Cache/IReachGraphCache.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Cache/ReachGraphCacheOptions.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Cache/ReachGraphValkeyCache.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Cache/StellaOps.ReachGraph.Cache.csproj create mode 100644 src/__Libraries/StellaOps.ReachGraph.Persistence/IReachGraphRepository.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Persistence/Migrations/001_reachgraph_store.sql create mode 100644 src/__Libraries/StellaOps.ReachGraph.Persistence/PostgresReachGraphRepository.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph.Persistence/StellaOps.ReachGraph.Persistence.csproj create mode 100644 src/__Libraries/StellaOps.ReachGraph/AGENTS.md create mode 100644 src/__Libraries/StellaOps.ReachGraph/Hashing/ReachGraphDigestComputer.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Schema/EdgeExplanation.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Schema/ReachGraphEdge.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Schema/ReachGraphMinimal.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Schema/ReachGraphNode.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Schema/ReachGraphProvenance.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Serialization/CanonicalReachGraphSerializer.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Signing/IReachGraphKeyStore.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Signing/IReachGraphSignerService.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/Signing/ReachGraphSignerService.cs create mode 100644 src/__Libraries/StellaOps.ReachGraph/StellaOps.ReachGraph.csproj create mode 100644 src/__Libraries/StellaOps.Replay.Core.Tests/Export/ReplayManifestExporterTests.cs create mode 100644 src/__Libraries/StellaOps.Replay.Core/Export/IReplayManifestExporter.cs create mode 100644 src/__Libraries/StellaOps.Replay.Core/Export/ReplayExportModels.cs create mode 100644 src/__Libraries/StellaOps.Replay.Core/Export/ReplayManifestExporter.cs create mode 100644 src/__Libraries/StellaOps.Replay.Core/Schemas/replay-export.schema.json create mode 100644 src/__Libraries/StellaOps.Verdict/AGENTS.md create mode 100644 src/__Libraries/StellaOps.Verdict/Api/VerdictContracts.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Api/VerdictEndpoints.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Contexts/verdict-1.0.jsonld create mode 100644 src/__Libraries/StellaOps.Verdict/Export/VerdictBundleExporter.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Oci/OciAttestationPublisher.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Persistence/IVerdictStore.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Persistence/Migrations/001_create_verdicts.sql create mode 100644 src/__Libraries/StellaOps.Verdict/Persistence/PostgresVerdictStore.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Persistence/VerdictRow.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Schema/StellaVerdict.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Services/VerdictAssemblyService.cs create mode 100644 src/__Libraries/StellaOps.Verdict/Services/VerdictSigningService.cs create mode 100644 src/__Libraries/StellaOps.Verdict/StellaOps.Verdict.csproj create mode 100644 src/__Libraries/__Tests/StellaOps.AuditPack.Tests/AuditPackExportServiceIntegrationTests.cs rename src/__Libraries/__Tests/{StellaOps.Evidence.Storage.Postgres.Tests => StellaOps.Evidence.Persistence.Tests}/CrossModuleEvidenceLinkingTests.cs (99%) create mode 100644 src/__Libraries/__Tests/StellaOps.Evidence.Persistence.Tests/Fixtures/EvidencePostgresContainerFixture.cs rename src/__Libraries/__Tests/{StellaOps.Evidence.Storage.Postgres.Tests => StellaOps.Evidence.Persistence.Tests}/PostgresEvidenceStoreIntegrationTests.cs (99%) create mode 100644 src/__Libraries/__Tests/StellaOps.Evidence.Persistence.Tests/StellaOps.Evidence.Persistence.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Evidence.Storage.Postgres.Tests/Fixtures/EvidencePostgresContainerFixture.cs delete mode 100644 src/__Libraries/__Tests/StellaOps.Evidence.Storage.Postgres.Tests/StellaOps.Evidence.Storage.Postgres.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Microservice.SourceGen.Tests/StellaOps.Microservice.SourceGen.Tests.csproj create mode 100644 src/__Libraries/__Tests/StellaOps.Plugin.Tests/PluginCompatibilityCheckerTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.Plugin.Tests/PluginHostOptionsTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.Plugin.Tests/PluginHostTests.cs rename src/{StellaOps.Events.Provenance.Tests => __Libraries/__Tests/StellaOps.Provenance.Tests}/ProvenanceExtensionsTests.cs (93%) create mode 100644 src/__Libraries/__Tests/StellaOps.Provenance.Tests/StellaOps.Provenance.Tests.csproj create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/CanonicalSerializerTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/DigestComputerTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/EdgeExplanationTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/Fixtures/feature-flag-guards.reachgraph.min.json create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/Fixtures/simple-single-path.reachgraph.min.json create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/GoldenSampleTests.cs create mode 100644 src/__Libraries/__Tests/StellaOps.ReachGraph.Tests/StellaOps.ReachGraph.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Router.Integration.Tests/StellaOps.Router.Integration.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Router.Transport.Tcp.Tests/StellaOps.Router.Transport.Tcp.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Router.Transport.Tls.Tests/StellaOps.Router.Transport.Tls.Tests.csproj delete mode 100644 src/__Libraries/__Tests/StellaOps.Router.Transport.Udp.Tests/StellaOps.Router.Transport.Udp.Tests.csproj delete mode 100644 src/__Tests/AirGap/README.md delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Controller.Tests/StellaOps.AirGap.Controller.Tests.csproj delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Quarantine/FileSystemQuarantineServiceTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/ArtifactIndexTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/CycloneDxParserTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/DsseAttestationParserTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/EvidenceDirectoryDiscoveryTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.cdx.json delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.intoto.json delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/Fixtures/sample.spdx.json delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/SourcePrecedenceLatticePropertyTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Reconciliation/SpdxParserTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/ReplayVerifierTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/RootRotationPolicyTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/StellaOps.AirGap.Importer.Tests.csproj delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/TufMetadataValidatorTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Validation/ImportValidatorIntegrationTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Validation/RekorOfflineReceiptVerifierTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Versioning/BundleVersionTests.cs delete mode 100644 src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests/Versioning/VersionMonotonicityCheckerTests.cs create mode 100644 src/__Tests/Integration/StellaOps.Integration.E2E/ReachGraphE2ETests.cs delete mode 100644 src/__Tests/Policy/StellaOps.Policy.Scoring.Tests/Fixtures/hashing/receipt-input.json delete mode 100644 src/__Tests/Policy/StellaOps.Policy.Scoring.Tests/Fixtures/hashing/receipt-input.sha256 delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/Fixtures/cosign.sig delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/SignersTests.cs delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/StellaOps.Provenance.Attestation.Tests.csproj delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/TestTimeProvider.cs delete mode 100644 src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests/VerificationLibraryTests.cs delete mode 100644 src/__Tests/Replay/StellaOps.Replay.Core.Tests/StellaOps.Replay.Core.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Gateway.WebService.Tests/StellaOps.Gateway.WebService.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Router.Common.Tests/FrameTypeTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Config.Tests/RouterConfigTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Config.Tests/StellaOps.Router.Config.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/ConnectionManagerTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/DefaultRoutingPluginTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/InMemoryRoutingStateTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/InMemoryValkeyRateLimitStoreTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/InstanceRateLimiterTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/IntegrationTestAttributes.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/LimitInheritanceResolverTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/MiddlewareErrorScenarioTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/Properties/RoutingDecisionPropertyTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/RateLimitConfigTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/RateLimitMiddlewareTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/RateLimitRouteMatcherTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/RateLimitServiceTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/RouterNodeConfigValidationTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/StellaOps.Router.Gateway.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/ValkeyRateLimitStoreIntegrationTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Gateway.Tests/ValkeyTestcontainerFixture.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/CancelFlowTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/HelloHeartbeatFlowTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryChannelTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/InMemoryConnectionRegistryTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/RequestResponseFlowTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/StellaOps.Router.Transport.InMemory.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Router.Transport.InMemory.Tests/StreamingFlowTests.cs delete mode 100644 src/__Tests/StellaOps.Router.Transport.Udp.Tests/StellaOps.Router.Transport.Udp.Tests.csproj delete mode 100644 src/__Tests/StellaOps.Router.Transport.Udp.Tests/UdpTransportTests.cs create mode 100644 src/__Tests/__Libraries/StellaOps.Infrastructure.Postgres.Testing/MigrationTestAttribute.cs delete mode 100644 src/__Tests/__Libraries/StellaOps.Messaging.Testing/StellaOps.Messaging.Testing.csproj create mode 100644 src/__Tests/unit/StellaOps.AuditPack.Tests/AuditPackExportServiceTests.cs create mode 100644 src/__Tests/unit/StellaOps.AuditPack.Tests/ReplayAttestationServiceTests.cs delete mode 100644 src/concelier-webservice.slnf create mode 100644 src/msbuild.pp.xml create mode 100644 src/nuget.config delete mode 100644 stryker-thresholds.json create mode 100644 tools/slntools/__init__.py create mode 100644 tools/slntools/lib/__init__.py create mode 100644 tools/slntools/lib/__pycache__/__init__.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/csproj_parser.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/dependency_graph.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/models.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/nuget_api.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/sln_writer.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/version_utils.cpython-313.pyc create mode 100644 tools/slntools/lib/__pycache__/vulnerability_models.cpython-313.pyc create mode 100644 tools/slntools/lib/csproj_parser.py create mode 100644 tools/slntools/lib/dependency_graph.py create mode 100644 tools/slntools/lib/models.py create mode 100644 tools/slntools/lib/nuget_api.py create mode 100644 tools/slntools/lib/sln_writer.py create mode 100644 tools/slntools/lib/version_utils.py create mode 100644 tools/slntools/lib/vulnerability_models.py create mode 100644 tools/slntools/nuget_centralizer.py create mode 100644 tools/slntools/nuget_normalizer.py create mode 100644 tools/slntools/nuget_vuln_checker.py create mode 100644 tools/slntools/sln_generator.py diff --git a/.actrc b/.actrc new file mode 100644 index 000000000..7a08ab5bd --- /dev/null +++ b/.actrc @@ -0,0 +1,47 @@ +# ============================================================================= +# ACT CONFIGURATION +# ============================================================================= +# Configuration for nektos/act - local Gitea/GitHub Actions runner. +# +# Usage: +# act # Run default event +# act pull_request # Run PR event +# act -W .gitea/workflows/test-matrix.yml +# act -l # List available jobs +# act -n # Dry run +# +# Installation: +# macOS: brew install act +# Linux: curl -sSL https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash +# Windows: choco install act-cli +# +# Documentation: https://github.com/nektos/act +# ============================================================================= + +# Platform mappings - use local CI image for consistent environment +--platform ubuntu-22.04=stellaops-ci:local +--platform ubuntu-latest=stellaops-ci:local + +# Container architecture (amd64 for consistency) +--container-architecture linux/amd64 + +# Environment variables matching CI +--env DOTNET_NOLOGO=1 +--env DOTNET_CLI_TELEMETRY_OPTOUT=1 +--env DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +--env TZ=UTC + +# Load local secrets/environment +--env-file devops/ci-local/.env.local + +# Bind mount the repository (faster than copying) +--bind + +# Reuse containers between runs (faster) +--reuse + +# Artifact server path for uploads +--artifact-server-path ./out/act-artifacts + +# Default event file +--eventpath devops/ci-local/events/pull-request.json diff --git a/.gitea/README.md b/.gitea/README.md new file mode 100644 index 000000000..6b414fb21 --- /dev/null +++ b/.gitea/README.md @@ -0,0 +1,279 @@ +# StellaOps CI/CD Infrastructure + +Comprehensive CI/CD infrastructure for the StellaOps platform using Gitea Actions. + +## Quick Reference + +| Resource | Location | +|----------|----------| +| Workflows | `.gitea/workflows/` (96 workflows) | +| Scripts | `.gitea/scripts/` | +| Documentation | `.gitea/docs/` | +| DevOps Configs | `devops/` | +| Release Manifests | `devops/releases/` | + +## Workflow Categories + +### Core Build & Test + +| Workflow | File | Description | +|----------|------|-------------| +| Build Test Deploy | `build-test-deploy.yml` | Main CI pipeline for all modules | +| Test Matrix | `test-matrix.yml` | Unified test execution with TRX reporting | +| Test Lanes | `test-lanes.yml` | Parallel test lane execution | +| Integration Tests | `integration-tests-gate.yml` | Integration test quality gate | + +### Release Pipelines + +| Workflow | File | Description | +|----------|------|-------------| +| Suite Release | `release-suite.yml` | Full platform release (YYYY.MM versioning) | +| Service Release | `service-release.yml` | Per-service release pipeline | +| Module Publish | `module-publish.yml` | NuGet and container publishing | +| Release Validation | `release-validation.yml` | Post-release verification | +| Promote | `promote.yml` | Environment promotion (dev/stage/prod) | + +### CLI & SDK + +| Workflow | File | Description | +|----------|------|-------------| +| CLI Build | `cli-build.yml` | Multi-platform CLI builds | +| CLI Chaos Parity | `cli-chaos-parity.yml` | CLI behavioral consistency tests | +| SDK Generator | `sdk-generator.yml` | Client SDK generation | +| SDK Publish | `sdk-publish.yml` | SDK package publishing | + +### Security & Compliance + +| Workflow | File | Description | +|----------|------|-------------| +| Artifact Signing | `artifact-signing.yml` | Cosign artifact signing | +| Dependency Security | `dependency-security-scan.yml` | Vulnerability scanning | +| License Audit | `license-audit.yml` | OSS license compliance | +| License Gate | `dependency-license-gate.yml` | PR license compliance gate | +| Crypto Compliance | `crypto-compliance.yml` | Cryptographic compliance checks | +| Provenance Check | `provenance-check.yml` | Supply chain provenance | + +### Attestation & Evidence + +| Workflow | File | Description | +|----------|------|-------------| +| Attestation Bundle | `attestation-bundle.yml` | in-toto attestation bundling | +| Evidence Locker | `evidence-locker.yml` | Evidence artifact storage | +| VEX Proof Bundles | `vex-proof-bundles.yml` | VEX proof generation | +| Signals Evidence | `signals-evidence-locker.yml` | Signal evidence collection | +| Signals DSSE Sign | `signals-dsse-sign.yml` | DSSE envelope signing | + +### Scanner & Analysis + +| Workflow | File | Description | +|----------|------|-------------| +| Scanner Analyzers | `scanner-analyzers.yml` | Language analyzer CI | +| Scanner Determinism | `scanner-determinism.yml` | Output reproducibility tests | +| Reachability Bench | `reachability-bench.yaml` | Reachability analysis benchmarks | +| Reachability Corpus | `reachability-corpus-ci.yml` | Corpus maintenance | +| EPSS Ingest Perf | `epss-ingest-perf.yml` | EPSS ingestion performance | + +### Determinism & Reproducibility + +| Workflow | File | Description | +|----------|------|-------------| +| Determinism Gate | `determinism-gate.yml` | Build determinism quality gate | +| Cross-Platform Det. | `cross-platform-determinism.yml` | Cross-OS reproducibility | +| Bench Determinism | `bench-determinism.yml` | Benchmark determinism | +| E2E Reproducibility | `e2e-reproducibility.yml` | End-to-end reproducibility | + +### Module-Specific + +| Workflow | File | Description | +|----------|------|-------------| +| Advisory AI Release | `advisory-ai-release.yml` | AI module release | +| AOC Guard | `aoc-guard.yml` | AOC policy enforcement | +| Authority Key Rotation | `authority-key-rotation.yml` | Key rotation automation | +| Concelier Tests | `concelier-attestation-tests.yml` | Concelier attestation tests | +| Findings Ledger | `findings-ledger-ci.yml` | Findings ledger CI | +| Policy Lint | `policy-lint.yml` | Policy DSL validation | +| Router Chaos | `router-chaos.yml` | Router chaos testing | +| Signals CI | `signals-ci.yml` | Signals module CI | + +### Infrastructure & Ops + +| Workflow | File | Description | +|----------|------|-------------| +| Containers Multiarch | `containers-multiarch.yml` | Multi-architecture builds | +| Docker Regional | `docker-regional-builds.yml` | Regional Docker builds | +| Helm Validation | (via scripts) | Helm chart validation | +| Console Runner | `console-runner-image.yml` | Runner image builds | +| Obs SLO | `obs-slo.yml` | Observability SLO checks | +| Obs Stream | `obs-stream.yml` | Telemetry streaming | + +### Documentation & API + +| Workflow | File | Description | +|----------|------|-------------| +| Docs | `docs.yml` | Documentation site build | +| OAS CI | `oas-ci.yml` | OpenAPI spec validation | +| API Governance | `api-governance.yml` | API governance checks | +| Schema Validation | `schema-validation.yml` | JSON schema validation | + +### Dependency Management + +| Workflow | File | Description | +|----------|------|-------------| +| Renovate | `renovate.yml` | Automated dependency updates | +| License Gate | `dependency-license-gate.yml` | License compliance gate | +| Security Scan | `dependency-security-scan.yml` | Vulnerability scanning | + +## Script Categories + +### Build Scripts (`scripts/build/`) + +| Script | Purpose | +|--------|---------| +| `build-cli.sh` | Build CLI for specific runtime | +| `build-multiarch.sh` | Multi-architecture container builds | +| `build-airgap-bundle.sh` | Air-gap deployment bundle | + +### Test Scripts (`scripts/test/`) + +| Script | Purpose | +|--------|---------| +| `determinism-run.sh` | Determinism verification | +| `run-fixtures-check.sh` | Test fixture validation | + +### Validation Scripts (`scripts/validate/`) + +| Script | Purpose | +|--------|---------| +| `validate-compose.sh` | Docker Compose validation | +| `validate-helm.sh` | Helm chart validation | +| `validate-licenses.sh` | License compliance | +| `validate-migrations.sh` | Database migration validation | +| `validate-sbom.sh` | SBOM validation | +| `validate-spdx.sh` | SPDX format validation | +| `validate-vex.sh` | VEX document validation | +| `validate-workflows.sh` | Workflow YAML validation | +| `verify-binaries.sh` | Binary integrity verification | + +### Signing Scripts (`scripts/sign/`) + +| Script | Purpose | +|--------|---------| +| `sign-authority-gaps.sh` | Sign authority gap attestations | +| `sign-policy.sh` | Sign policy artifacts | +| `sign-signals.sh` | Sign signals data | + +### Release Scripts (`scripts/release/`) + +| Script | Purpose | +|--------|---------| +| `build_release.py` | Suite release orchestration | +| `verify_release.py` | Release verification | +| `bump-service-version.py` | Service version management | +| `read-service-version.sh` | Read current version | +| `generate-docker-tag.sh` | Generate Docker tags | +| `generate_changelog.py` | AI-assisted changelog | +| `generate_suite_docs.py` | Release documentation | +| `generate_compose.py` | Docker Compose generation | +| `collect_versions.py` | Version collection | +| `check_cli_parity.py` | CLI version parity | + +### Evidence Scripts (`scripts/evidence/`) + +| Script | Purpose | +|--------|---------| +| `upload-all-evidence.sh` | Upload all evidence bundles | +| `signals-upload-evidence.sh` | Upload signals evidence | +| `zastava-upload-evidence.sh` | Upload Zastava evidence | + +### Metrics Scripts (`scripts/metrics/`) + +| Script | Purpose | +|--------|---------| +| `compute-reachability-metrics.sh` | Reachability analysis metrics | +| `compute-ttfs-metrics.sh` | Time-to-first-scan metrics | +| `enforce-performance-slos.sh` | SLO enforcement | + +### Utility Scripts (`scripts/util/`) + +| Script | Purpose | +|--------|---------| +| `cleanup-runner-space.sh` | Runner disk cleanup | +| `dotnet-filter.sh` | .NET project filtering | +| `enable-openssl11-shim.sh` | OpenSSL 1.1 compatibility | + +## Environment Variables + +### Required Secrets + +| Secret | Purpose | Workflows | +|--------|---------|-----------| +| `GITEA_TOKEN` | API access, commits | All | +| `RENOVATE_TOKEN` | Dependency bot access | `renovate.yml` | +| `COSIGN_PRIVATE_KEY_B64` | Artifact signing | Release pipelines | +| `AI_API_KEY` | Changelog generation | `release-suite.yml` | +| `REGISTRY_USERNAME` | Container registry | Build/deploy | +| `REGISTRY_PASSWORD` | Container registry | Build/deploy | +| `SSH_PRIVATE_KEY` | Deployment access | Deploy pipelines | + +### Common Variables + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DOTNET_VERSION` | `10.0.100` | .NET SDK version | +| `NODE_VERSION` | `20` | Node.js version | +| `RENOVATE_VERSION` | `37.100.0` | Renovate version | +| `REGISTRY_HOST` | `git.stella-ops.org` | Container registry | + +## Versioning Strategy + +### Suite Releases (Platform) + +- Format: `YYYY.MM` with codenames (Ubuntu-style) +- Example: `2026.04 Nova` +- Triggered by: Tag `suite-YYYY.MM` +- Documentation: `docs/releases/YYYY.MM/` + +### Service Releases (Individual) + +- Format: SemVer `MAJOR.MINOR.PATCH` +- Docker tag: `{version}+{YYYYMMDDHHmmss}` +- Example: `1.2.3+20250128143022` +- Triggered by: Tag `service-{name}-v{version}` +- Version source: `src/Directory.Versions.props` + +### Module Releases + +- Format: SemVer `MAJOR.MINOR.PATCH` +- Triggered by: Tag `module-{name}-v{version}` + +## Documentation + +| Document | Description | +|----------|-------------| +| [Architecture](docs/architecture.md) | Workflow architecture and dependencies | +| [Scripts Inventory](docs/scripts.md) | Complete script documentation | +| [Troubleshooting](docs/troubleshooting.md) | Common issues and solutions | +| [Development Guide](docs/development.md) | Creating new workflows | +| [Runners](docs/runners.md) | Self-hosted runner setup | +| [Dependency Management](docs/dependency-management.md) | Renovate guide | + +## Related Documentation + +- [Main Architecture](../docs/07_HIGH_LEVEL_ARCHITECTURE.md) +- [DevOps README](../devops/README.md) +- [Release Versioning](../docs/releases/VERSIONING.md) +- [Offline Operations](../docs/24_OFFLINE_KIT.md) + +## Contributing + +1. Read `AGENTS.md` before making changes +2. Follow workflow naming conventions +3. Pin tool versions where possible +4. Keep workflows deterministic and offline-friendly +5. Update documentation when adding/modifying workflows +6. Test locally with `act` when possible + +## Support + +- Issues: https://git.stella-ops.org/stella-ops.org/issues +- Documentation: `docs/` diff --git a/.gitea/config/path-filters.yml b/.gitea/config/path-filters.yml new file mode 100644 index 000000000..9ec56be6c --- /dev/null +++ b/.gitea/config/path-filters.yml @@ -0,0 +1,533 @@ +# ============================================================================= +# CENTRALIZED PATH FILTER DEFINITIONS +# ============================================================================= +# This file documents the path filters used across all CI/CD workflows. +# Each workflow should reference these patterns for consistency. +# +# Last updated: 2025-12-28 +# ============================================================================= + +# ----------------------------------------------------------------------------- +# INFRASTRUCTURE FILES - Changes trigger FULL CI +# ----------------------------------------------------------------------------- +infrastructure: + - 'Directory.Build.props' + - 'Directory.Build.rsp' + - 'Directory.Packages.props' + - 'src/Directory.Build.props' + - 'src/Directory.Packages.props' + - 'nuget.config' + - 'StellaOps.sln' + +# ----------------------------------------------------------------------------- +# DOCUMENTATION - Should NOT trigger builds (paths-ignore) +# ----------------------------------------------------------------------------- +docs_ignore: + - 'docs/**' + - '*.md' + - '!CLAUDE.md' # Exception: Agent instructions SHOULD trigger + - '!AGENTS.md' # Exception: Module guidance SHOULD trigger + - 'etc/**' + - 'LICENSE' + - '.gitignore' + - '.editorconfig' + +# ----------------------------------------------------------------------------- +# SHARED LIBRARIES - Trigger cascading tests +# ----------------------------------------------------------------------------- +shared_libraries: + # Cryptography - CRITICAL, affects all security modules + cryptography: + paths: + - 'src/__Libraries/StellaOps.Cryptography*/**' + - 'src/Cryptography/**' + cascades_to: + - scanner + - attestor + - authority + - evidence_locker + - signer + - airgap + + # Evidence & Provenance - Affects attestation chain + evidence: + paths: + - 'src/__Libraries/StellaOps.Evidence*/**' + - 'src/__Libraries/StellaOps.Provenance/**' + cascades_to: + - scanner + - attestor + - evidence_locker + - export_center + - sbom_service + + # Infrastructure - Affects all database-backed modules + infrastructure: + paths: + - 'src/__Libraries/StellaOps.Infrastructure*/**' + - 'src/__Libraries/StellaOps.DependencyInjection/**' + cascades_to: + - all_integration_tests + + # Replay & Determinism - Affects reproducibility tests + replay: + paths: + - 'src/__Libraries/StellaOps.Replay*/**' + - 'src/__Libraries/StellaOps.Testing.Determinism/**' + cascades_to: + - scanner + - determinism_tests + - replay + + # Verdict & Policy Primitives + verdict: + paths: + - 'src/__Libraries/StellaOps.Verdict/**' + - 'src/__Libraries/StellaOps.DeltaVerdict/**' + cascades_to: + - policy + - risk_engine + - reach_graph + + # Plugin Framework + plugin: + paths: + - 'src/__Libraries/StellaOps.Plugin/**' + cascades_to: + - authority + - scanner + - concelier + + # Configuration + configuration: + paths: + - 'src/__Libraries/StellaOps.Configuration/**' + cascades_to: + - all_modules + +# ----------------------------------------------------------------------------- +# MODULE PATHS - Each module with its source and test paths +# ----------------------------------------------------------------------------- +modules: + # Scanning & Analysis + scanner: + source: + - 'src/Scanner/**' + - 'src/BinaryIndex/**' + tests: + - 'src/Scanner/__Tests/**' + - 'src/BinaryIndex/__Tests/**' + workflows: + - 'scanner-*.yml' + - 'scanner-analyzers*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Evidence*/**' + - 'src/__Libraries/StellaOps.Cryptography*/**' + - 'src/__Libraries/StellaOps.Replay*/**' + - 'src/__Libraries/StellaOps.Provenance/**' + + binary_index: + source: + - 'src/BinaryIndex/**' + tests: + - 'src/BinaryIndex/__Tests/**' + + # Data Ingestion + concelier: + source: + - 'src/Concelier/**' + tests: + - 'src/Concelier/__Tests/**' + workflows: + - 'concelier-*.yml' + - 'connector-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Plugin/**' + + excititor: + source: + - 'src/Excititor/**' + tests: + - 'src/Excititor/__Tests/**' + workflows: + - 'vex-*.yml' + - 'export-*.yml' + + vexlens: + source: + - 'src/VexLens/**' + tests: + - 'src/VexLens/__Tests/**' + + vexhub: + source: + - 'src/VexHub/**' + tests: + - 'src/VexHub/__Tests/**' + + # Core Platform + authority: + source: + - 'src/Authority/**' + tests: + - 'src/Authority/__Tests/**' + workflows: + - 'authority-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Cryptography*/**' + - 'src/__Libraries/StellaOps.Plugin/**' + + gateway: + source: + - 'src/Gateway/**' + tests: + - 'src/Gateway/__Tests/**' + + router: + source: + - 'src/Router/**' + tests: + - 'src/Router/__Tests/**' + workflows: + - 'router-*.yml' + + # Artifacts & Evidence + attestor: + source: + - 'src/Attestor/**' + tests: + - 'src/Attestor/__Tests/**' + workflows: + - 'attestation-*.yml' + - 'attestor-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Cryptography*/**' + - 'src/__Libraries/StellaOps.Evidence*/**' + - 'src/__Libraries/StellaOps.Provenance/**' + + sbom_service: + source: + - 'src/SbomService/**' + tests: + - 'src/SbomService/__Tests/**' + dependencies: + - 'src/__Libraries/StellaOps.Evidence*/**' + + evidence_locker: + source: + - 'src/EvidenceLocker/**' + tests: + - 'src/EvidenceLocker/__Tests/**' + workflows: + - 'evidence-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Evidence*/**' + - 'src/__Libraries/StellaOps.Cryptography*/**' + + export_center: + source: + - 'src/ExportCenter/**' + tests: + - 'src/ExportCenter/__Tests/**' + workflows: + - 'export-*.yml' + + findings: + source: + - 'src/Findings/**' + tests: + - 'src/Findings/__Tests/**' + workflows: + - 'findings-*.yml' + - 'ledger-*.yml' + + provenance: + source: + - 'src/Provenance/**' + tests: + - 'src/Provenance/__Tests/**' + workflows: + - 'provenance-*.yml' + + signer: + source: + - 'src/Signer/**' + tests: + - 'src/Signer/__Tests/**' + dependencies: + - 'src/__Libraries/StellaOps.Cryptography*/**' + + # Policy & Risk + policy: + source: + - 'src/Policy/**' + tests: + - 'src/Policy/__Tests/**' + workflows: + - 'policy-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Verdict/**' + + risk_engine: + source: + - 'src/RiskEngine/**' + tests: + - 'src/RiskEngine/__Tests/**' + dependencies: + - 'src/__Libraries/StellaOps.Verdict/**' + + reach_graph: + source: + - 'src/ReachGraph/**' + tests: + - 'src/ReachGraph/__Tests/**' + workflows: + - 'reachability-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.ReachGraph*/**' + + # Operations + notify: + source: + - 'src/Notify/**' + - 'src/Notifier/**' + tests: + - 'src/Notify/__Tests/**' + workflows: + - 'notify-*.yml' + + orchestrator: + source: + - 'src/Orchestrator/**' + tests: + - 'src/Orchestrator/__Tests/**' + + scheduler: + source: + - 'src/Scheduler/**' + tests: + - 'src/Scheduler/__Tests/**' + + task_runner: + source: + - 'src/TaskRunner/**' + tests: + - 'src/TaskRunner/__Tests/**' + + packs_registry: + source: + - 'src/PacksRegistry/**' + tests: + - 'src/PacksRegistry/__Tests/**' + workflows: + - 'packs-*.yml' + + replay: + source: + - 'src/Replay/**' + tests: + - 'src/Replay/__Tests/**' + workflows: + - 'replay-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Replay*/**' + + # Infrastructure + cryptography: + source: + - 'src/Cryptography/**' + tests: + - 'src/__Libraries/__Tests/StellaOps.Cryptography*/**' + workflows: + - 'crypto-*.yml' + + telemetry: + source: + - 'src/Telemetry/**' + tests: + - 'src/Telemetry/__Tests/**' + + signals: + source: + - 'src/Signals/**' + tests: + - 'src/Signals/__Tests/**' + workflows: + - 'signals-*.yml' + + airgap: + source: + - 'src/AirGap/**' + tests: + - 'src/AirGap/__Tests/**' + workflows: + - 'airgap-*.yml' + - 'offline-*.yml' + dependencies: + - 'src/__Libraries/StellaOps.Cryptography*/**' + + aoc: + source: + - 'src/Aoc/**' + tests: + - 'src/Aoc/__Tests/**' + workflows: + - 'aoc-*.yml' + + # Integration + cli: + source: + - 'src/Cli/**' + tests: + - 'src/Cli/__Tests/**' + workflows: + - 'cli-*.yml' + + web: + source: + - 'src/Web/**' + tests: + - 'src/Web/**/*.spec.ts' + workflows: + - 'lighthouse-*.yml' + + issuer_directory: + source: + - 'src/IssuerDirectory/**' + tests: + - 'src/IssuerDirectory/__Tests/**' + + mirror: + source: + - 'src/Mirror/**' + tests: + - 'src/Mirror/__Tests/**' + workflows: + - 'mirror-*.yml' + + advisory_ai: + source: + - 'src/AdvisoryAI/**' + tests: + - 'src/AdvisoryAI/__Tests/**' + workflows: + - 'advisory-*.yml' + + symbols: + source: + - 'src/Symbols/**' + tests: + - 'src/Symbols/__Tests/**' + workflows: + - 'symbols-*.yml' + + graph: + source: + - 'src/Graph/**' + tests: + - 'src/Graph/__Tests/**' + workflows: + - 'graph-*.yml' + +# ----------------------------------------------------------------------------- +# DEVOPS & CI/CD - Changes affecting infrastructure +# ----------------------------------------------------------------------------- +devops: + docker: + - 'devops/docker/**' + - '**/Dockerfile' + compose: + - 'devops/compose/**' + helm: + - 'devops/helm/**' + database: + - 'devops/database/**' + scripts: + - '.gitea/scripts/**' + workflows: + - '.gitea/workflows/**' + +# ----------------------------------------------------------------------------- +# TEST INFRASTRUCTURE +# ----------------------------------------------------------------------------- +test_infrastructure: + global_tests: + - 'src/__Tests/**' + shared_libraries: + - 'src/__Tests/__Libraries/**' + datasets: + - 'src/__Tests/__Datasets/**' + benchmarks: + - 'src/__Tests/__Benchmarks/**' + +# ----------------------------------------------------------------------------- +# TRIGGER CATEGORY DEFINITIONS +# ----------------------------------------------------------------------------- +# Reference for which workflows belong to each trigger category + +categories: + # Category A: PR-Gating (MUST PASS for merge) + pr_gating: + trigger: 'pull_request + push to main' + workflows: + - build-test-deploy.yml + - test-matrix.yml + - determinism-gate.yml + - policy-lint.yml + - sast-scan.yml + - secrets-scan.yml + - dependency-license-gate.yml + + # Category B: Main-Branch Only (Post-merge verification) + main_only: + trigger: 'push to main only' + workflows: + - container-scan.yml + - integration-tests-gate.yml + - api-governance.yml + - aoc-guard.yml + - provenance-check.yml + - manifest-integrity.yml + + # Category C: Module-Specific (Selective by path) + module_specific: + trigger: 'PR + main with path filters' + patterns: + - 'scanner-*.yml' + - 'concelier-*.yml' + - 'authority-*.yml' + - 'attestor-*.yml' + - 'policy-*.yml' + - 'evidence-*.yml' + - 'export-*.yml' + - 'notify-*.yml' + - 'router-*.yml' + - 'crypto-*.yml' + + # Category D: Release/Deploy (Tag or Manual only) + release: + trigger: 'tags or workflow_dispatch only' + workflows: + - release-suite.yml + - module-publish.yml + - service-release.yml + - cli-build.yml + - containers-multiarch.yml + - rollback.yml + - promote.yml + tag_patterns: + suite: 'suite-*' + module: 'module-*-v*' + service: 'service-*-v*' + cli: 'cli-v*' + bundle: 'v*.*.*' + + # Category E: Scheduled (Nightly/Weekly) + scheduled: + workflows: + - nightly-regression.yml # Daily 2:00 UTC + - dependency-security-scan.yml # Weekly Sun 2:00 UTC + - container-scan.yml # Daily 4:00 UTC (also main-only) + - sast-scan.yml # Weekly Mon 3:30 UTC + - renovate.yml # Daily 3:00, 15:00 UTC + - benchmark-vs-competitors.yml # Weekly Sat 1:00 UTC diff --git a/.gitea/docs/architecture.md b/.gitea/docs/architecture.md new file mode 100644 index 000000000..860cdee06 --- /dev/null +++ b/.gitea/docs/architecture.md @@ -0,0 +1,432 @@ +# CI/CD Architecture + +> **Extended Documentation:** See [docs/cicd/](../../docs/cicd/) for comprehensive CI/CD guides. + +## Overview + +StellaOps CI/CD infrastructure is built on Gitea Actions with a modular, layered architecture designed for: +- **Determinism**: Reproducible builds and tests across environments +- **Offline-first**: Support for air-gapped deployments +- **Security**: Cryptographic signing and attestation at every stage +- **Scalability**: Parallel execution with intelligent caching + +## Quick Links + +| Document | Purpose | +|----------|---------| +| [CI/CD Overview](../../docs/cicd/README.md) | High-level architecture and getting started | +| [Workflow Triggers](../../docs/cicd/workflow-triggers.md) | Complete trigger matrix and dependency chains | +| [Release Pipelines](../../docs/cicd/release-pipelines.md) | Suite, module, and bundle release flows | +| [Security Scanning](../../docs/cicd/security-scanning.md) | SAST, secrets, container, and dependency scanning | +| [Troubleshooting](./troubleshooting.md) | Common issues and solutions | +| [Script Reference](./scripts.md) | CI/CD script documentation | + +## Workflow Trigger Summary + +### Trigger Matrix (100 Workflows) + +| Trigger Type | Count | Examples | +|--------------|-------|----------| +| PR + Main Push | 15 | `test-matrix.yml`, `build-test-deploy.yml` | +| Tag-Based | 3 | `release-suite.yml`, `release.yml`, `module-publish.yml` | +| Scheduled | 8 | `nightly-regression.yml`, `renovate.yml` | +| Manual Only | 25+ | `rollback.yml`, `cli-build.yml` | +| Module-Specific | 50+ | Scanner, Concelier, Authority workflows | + +### Tag Patterns + +| Pattern | Workflow | Example | +|---------|----------|---------| +| `suite-*` | Suite release | `suite-2026.04` | +| `v*` | Bundle release | `v2025.12.1` | +| `module-*-v*` | Module publish | `module-authority-v1.2.3` | + +### Schedule Overview + +| Time (UTC) | Workflow | Purpose | +|------------|----------|---------| +| 2:00 AM Daily | `nightly-regression.yml` | Full regression | +| 3:00 AM/PM Daily | `renovate.yml` | Dependency updates | +| 3:30 AM Monday | `sast-scan.yml` | Weekly security scan | +| 5:00 AM Daily | `test-matrix.yml` | Extended tests | + +> **Full Details:** See [Workflow Triggers](../../docs/cicd/workflow-triggers.md) + +## Pipeline Architecture + +### Release Pipeline Flow + +```mermaid +graph TD + subgraph "Trigger Layer" + TAG[Git Tag] --> PARSE[Parse Tag] + DISPATCH[Manual Dispatch] --> PARSE + SCHEDULE[Scheduled] --> PARSE + end + + subgraph "Validation Layer" + PARSE --> VALIDATE[Validate Inputs] + VALIDATE --> RESOLVE[Resolve Versions] + end + + subgraph "Build Layer" + RESOLVE --> BUILD[Build Modules] + BUILD --> TEST[Run Tests] + TEST --> DETERMINISM[Determinism Check] + end + + subgraph "Artifact Layer" + DETERMINISM --> CONTAINER[Build Container] + CONTAINER --> SBOM[Generate SBOM] + SBOM --> SIGN[Sign Artifacts] + end + + subgraph "Release Layer" + SIGN --> MANIFEST[Update Manifest] + MANIFEST --> CHANGELOG[Generate Changelog] + CHANGELOG --> DOCS[Generate Docs] + DOCS --> PUBLISH[Publish Release] + end + + subgraph "Post-Release" + PUBLISH --> VERIFY[Verify Release] + VERIFY --> NOTIFY[Notify Stakeholders] + end +``` + +### Service Release Pipeline + +```mermaid +graph LR + subgraph "Trigger" + A[service-{name}-v{semver}] --> B[Parse Service & Version] + end + + subgraph "Build" + B --> C[Read Directory.Versions.props] + C --> D[Bump Version] + D --> E[Build Service] + E --> F[Run Tests] + end + + subgraph "Package" + F --> G[Build Container] + G --> H[Generate Docker Tag] + H --> I[Push to Registry] + end + + subgraph "Attestation" + I --> J[Generate SBOM] + J --> K[Sign with Cosign] + K --> L[Create Attestation] + end + + subgraph "Finalize" + L --> M[Update Manifest] + M --> N[Commit Changes] + end +``` + +### Test Matrix Execution + +```mermaid +graph TD + subgraph "Matrix Strategy" + TRIGGER[PR/Push] --> FILTER[Path Filter] + FILTER --> MATRIX[Generate Matrix] + end + + subgraph "Parallel Execution" + MATRIX --> UNIT[Unit Tests] + MATRIX --> INT[Integration Tests] + MATRIX --> DET[Determinism Tests] + end + + subgraph "Test Types" + UNIT --> UNIT_FAST[Fast Unit] + UNIT --> UNIT_SLOW[Slow Unit] + INT --> INT_PG[PostgreSQL] + INT --> INT_VALKEY[Valkey] + DET --> DET_SCANNER[Scanner] + DET --> DET_BUILD[Build Output] + end + + subgraph "Reporting" + UNIT_FAST --> TRX[TRX Reports] + UNIT_SLOW --> TRX + INT_PG --> TRX + INT_VALKEY --> TRX + DET_SCANNER --> TRX + DET_BUILD --> TRX + TRX --> SUMMARY[Job Summary] + end +``` + +## Workflow Dependencies + +### Core Dependencies + +```mermaid +graph TD + BTD[build-test-deploy.yml] --> TM[test-matrix.yml] + BTD --> DG[determinism-gate.yml] + + TM --> TL[test-lanes.yml] + TM --> ITG[integration-tests-gate.yml] + + RS[release-suite.yml] --> BTD + RS --> MP[module-publish.yml] + RS --> AS[artifact-signing.yml] + + SR[service-release.yml] --> BTD + SR --> AS + + MP --> AS + MP --> AB[attestation-bundle.yml] +``` + +### Security Chain + +```mermaid +graph LR + BUILD[Build] --> SBOM[SBOM Generation] + SBOM --> SIGN[Cosign Signing] + SIGN --> ATTEST[Attestation] + ATTEST --> VERIFY[Verification] + VERIFY --> PUBLISH[Publish] +``` + +## Execution Stages + +### Stage 1: Validation + +| Step | Purpose | Tools | +|------|---------|-------| +| Parse trigger | Extract tag/input parameters | bash | +| Validate config | Check required files exist | bash | +| Resolve versions | Read from Directory.Versions.props | Python | +| Check permissions | Verify secrets available | Gitea Actions | + +### Stage 2: Build + +| Step | Purpose | Tools | +|------|---------|-------| +| Restore packages | NuGet/npm dependencies | dotnet restore, npm ci | +| Build solution | Compile all projects | dotnet build | +| Run analyzers | Code analysis | dotnet analyzers | + +### Stage 3: Test + +| Step | Purpose | Tools | +|------|---------|-------| +| Unit tests | Component testing | xUnit | +| Integration tests | Service integration | Testcontainers | +| Determinism tests | Output reproducibility | Custom scripts | + +### Stage 4: Package + +| Step | Purpose | Tools | +|------|---------|-------| +| Build container | Docker image | docker build | +| Generate SBOM | Software bill of materials | Syft | +| Sign artifacts | Cryptographic signing | Cosign | +| Create attestation | in-toto/DSSE envelope | Custom tools | + +### Stage 5: Publish + +| Step | Purpose | Tools | +|------|---------|-------| +| Push container | Registry upload | docker push | +| Upload attestation | Rekor transparency | Cosign | +| Update manifest | Version tracking | Python | +| Generate docs | Release documentation | Python | + +## Concurrency Control + +### Strategy + +```yaml +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +``` + +### Workflow Groups + +| Group | Behavior | Workflows | +|-------|----------|-----------| +| Build | Cancel in-progress | `build-test-deploy.yml` | +| Release | No cancel (sequential) | `release-suite.yml` | +| Deploy | Environment-locked | `promote.yml` | +| Scheduled | Allow concurrent | `renovate.yml` | + +## Caching Strategy + +### Cache Layers + +```mermaid +graph TD + subgraph "Package Cache" + NUGET[NuGet Cache
~/.nuget/packages] + NPM[npm Cache
~/.npm] + end + + subgraph "Build Cache" + OBJ[Object Files
**/obj] + BIN[Binaries
**/bin] + end + + subgraph "Test Cache" + TC[Testcontainers
Images] + FIX[Test Fixtures] + end + + subgraph "Keys" + K1[runner.os-nuget-hash] --> NUGET + K2[runner.os-npm-hash] --> NPM + K3[runner.os-dotnet-hash] --> OBJ + K3 --> BIN + end +``` + +### Cache Configuration + +| Cache | Key Pattern | Restore Keys | +|-------|-------------|--------------| +| NuGet | `${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}` | `${{ runner.os }}-nuget-` | +| npm | `${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}` | `${{ runner.os }}-npm-` | +| .NET Build | `${{ runner.os }}-dotnet-${{ github.sha }}` | `${{ runner.os }}-dotnet-` | + +## Runner Requirements + +### Self-Hosted Runners + +| Label | Purpose | Requirements | +|-------|---------|--------------| +| `ubuntu-latest` | General builds | 4 CPU, 16GB RAM, 100GB disk | +| `linux-arm64` | ARM builds | ARM64 host | +| `windows-latest` | Windows builds | Windows Server 2022 | +| `macos-latest` | macOS builds | macOS 13+ | + +### Docker-in-Docker + +Required for: +- Testcontainers integration tests +- Multi-architecture builds +- Container scanning + +### Network Requirements + +| Endpoint | Purpose | Required | +|----------|---------|----------| +| `git.stella-ops.org` | Source, Registry | Always | +| `nuget.org` | NuGet packages | Online mode | +| `registry.npmjs.org` | npm packages | Online mode | +| `ghcr.io` | GitHub Container Registry | Optional | + +## Artifact Flow + +### Build Artifacts + +``` +artifacts/ +├── binaries/ +│ ├── StellaOps.Cli-linux-x64 +│ ├── StellaOps.Cli-linux-arm64 +│ ├── StellaOps.Cli-win-x64 +│ └── StellaOps.Cli-osx-arm64 +├── containers/ +│ ├── scanner:1.2.3+20250128143022 +│ └── authority:1.0.0+20250128143022 +├── sbom/ +│ ├── scanner.cyclonedx.json +│ └── authority.cyclonedx.json +└── attestations/ + ├── scanner.intoto.jsonl + └── authority.intoto.jsonl +``` + +### Release Artifacts + +``` +docs/releases/2026.04/ +├── README.md +├── CHANGELOG.md +├── services.md +├── docker-compose.yml +├── docker-compose.airgap.yml +├── upgrade-guide.md +├── checksums.txt +└── manifest.yaml +``` + +## Error Handling + +### Retry Strategy + +| Step Type | Retries | Backoff | +|-----------|---------|---------| +| Network calls | 3 | Exponential | +| Docker push | 3 | Linear (30s) | +| Tests | 0 | N/A | +| Signing | 2 | Linear (10s) | + +### Failure Actions + +| Failure Type | Action | +|--------------|--------| +| Build failure | Fail fast, notify | +| Test failure | Continue, report | +| Signing failure | Fail, alert security | +| Deploy failure | Rollback, notify | + +## Security Architecture + +### Secret Management + +```mermaid +graph TD + subgraph "Gitea Secrets" + GS[Organization Secrets] + RS[Repository Secrets] + ES[Environment Secrets] + end + + subgraph "Usage" + GS --> BUILD[Build Workflows] + RS --> SIGN[Signing Workflows] + ES --> DEPLOY[Deploy Workflows] + end + + subgraph "Rotation" + ROTATE[Key Rotation] --> RS + ROTATE --> ES + end +``` + +### Signing Chain + +1. **Build outputs**: SHA-256 checksums +2. **Container images**: Cosign keyless/keyed signing +3. **SBOMs**: in-toto attestation +4. **Releases**: GPG-signed tags + +## Monitoring & Observability + +### Workflow Metrics + +| Metric | Source | Dashboard | +|--------|--------|-----------| +| Build duration | Gitea Actions | Grafana | +| Test pass rate | TRX reports | Grafana | +| Cache hit rate | Actions cache | Prometheus | +| Artifact size | Upload artifact | Prometheus | + +### Alerts + +| Alert | Condition | Action | +|-------|-----------|--------| +| Build time > 30m | Duration threshold | Investigate | +| Test failures > 5% | Rate threshold | Review | +| Cache miss streak | 3 consecutive | Clear cache | +| Security scan critical | Any critical CVE | Block merge | diff --git a/.gitea/docs/scripts.md b/.gitea/docs/scripts.md new file mode 100644 index 000000000..aff68771c --- /dev/null +++ b/.gitea/docs/scripts.md @@ -0,0 +1,736 @@ +# CI/CD Scripts Inventory + +Complete documentation of all scripts in `.gitea/scripts/`. + +## Directory Structure + +``` +.gitea/scripts/ +├── build/ # Build orchestration +├── evidence/ # Evidence bundle management +├── metrics/ # Performance metrics +├── release/ # Release automation +├── sign/ # Artifact signing +├── test/ # Test execution +├── util/ # Utilities +└── validate/ # Validation scripts +``` + +## Exit Code Conventions + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 1 | General error | +| 2 | Missing configuration/key | +| 3 | Missing required file | +| 69 | Tool not found (EX_UNAVAILABLE) | + +--- + +## Build Scripts (`scripts/build/`) + +### build-cli.sh + +Multi-platform CLI build with SBOM generation and signing. + +**Usage:** +```bash +RIDS=linux-x64,win-x64,osx-arm64 ./build-cli.sh +``` + +**Environment Variables:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `RIDS` | `linux-x64,win-x64,osx-arm64` | Comma-separated runtime identifiers | +| `CONFIG` | `Release` | Build configuration | +| `SBOM_TOOL` | `syft` | SBOM generator (`syft` or `none`) | +| `SIGN` | `false` | Enable artifact signing | +| `COSIGN_KEY` | - | Path to Cosign key file | + +**Output:** +``` +out/cli/ +├── linux-x64/ +│ ├── publish/ +│ ├── stella-cli-linux-x64.tar.gz +│ ├── stella-cli-linux-x64.tar.gz.sha256 +│ └── stella-cli-linux-x64.tar.gz.sbom.json +├── win-x64/ +│ ├── publish/ +│ ├── stella-cli-win-x64.zip +│ └── ... +└── manifest.json +``` + +**Features:** +- Builds self-contained single-file executables +- Includes CLI plugins (Aoc, Symbols) +- Generates SHA-256 checksums +- Optional SBOM generation via Syft +- Optional Cosign signing + +--- + +### build-multiarch.sh + +Multi-architecture Docker image builds using buildx. + +**Usage:** +```bash +IMAGE=scanner PLATFORMS=linux/amd64,linux/arm64 ./build-multiarch.sh +``` + +**Environment Variables:** + +| Variable | Default | Description | +|----------|---------|-------------| +| `IMAGE` | - | Image name (required) | +| `PLATFORMS` | `linux/amd64,linux/arm64` | Target platforms | +| `REGISTRY` | `git.stella-ops.org` | Container registry | +| `TAG` | `latest` | Image tag | +| `PUSH` | `false` | Push to registry | + +--- + +### build-airgap-bundle.sh + +Build offline/air-gapped deployment bundle. + +**Usage:** +```bash +VERSION=2026.04 ./build-airgap-bundle.sh +``` + +**Output:** +``` +out/airgap/ +├── images.tar # All container images +├── helm-charts.tar.gz # Helm charts +├── compose.tar.gz # Docker Compose files +├── checksums.txt +└── manifest.json +``` + +--- + +## Test Scripts (`scripts/test/`) + +### determinism-run.sh + +Run determinism verification tests. + +**Usage:** +```bash +./determinism-run.sh +``` + +**Purpose:** +- Executes tests filtered by `Determinism` category +- Collects TRX test results +- Generates summary and artifacts archive + +**Output:** +``` +out/scanner-determinism/ +├── determinism.trx +├── summary.txt +└── determinism-artifacts.tgz +``` + +--- + +### run-fixtures-check.sh + +Validate test fixtures against expected schemas. + +**Usage:** +```bash +./run-fixtures-check.sh [--update] +``` + +**Options:** +- `--update`: Update golden fixtures if mismatched + +--- + +## Validation Scripts (`scripts/validate/`) + +### validate-sbom.sh + +Validate CycloneDX SBOM files. + +**Usage:** +```bash +./validate-sbom.sh +./validate-sbom.sh --all +./validate-sbom.sh --schema custom.json sample.json +``` + +**Options:** + +| Option | Description | +|--------|-------------| +| `--all` | Validate all fixtures in `src/__Tests/__Benchmarks/golden-corpus/` | +| `--schema ` | Custom schema file | + +**Dependencies:** +- `sbom-utility` (auto-installed if missing) + +**Exit Codes:** +- `0`: All validations passed +- `1`: Validation failed + +--- + +### validate-spdx.sh + +Validate SPDX SBOM files. + +**Usage:** +```bash +./validate-spdx.sh +``` + +--- + +### validate-vex.sh + +Validate VEX documents (OpenVEX, CSAF). + +**Usage:** +```bash +./validate-vex.sh +``` + +--- + +### validate-helm.sh + +Validate Helm charts. + +**Usage:** +```bash +./validate-helm.sh [chart-path] +``` + +**Default Path:** `devops/helm/stellaops` + +**Checks:** +- `helm lint` +- Template rendering +- Schema validation + +--- + +### validate-compose.sh + +Validate Docker Compose files. + +**Usage:** +```bash +./validate-compose.sh [profile] +``` + +**Profiles:** +- `dev` - Development +- `stage` - Staging +- `prod` - Production +- `airgap` - Air-gapped + +--- + +### validate-licenses.sh + +Check dependency licenses for compliance. + +**Usage:** +```bash +./validate-licenses.sh +``` + +**Checks:** +- NuGet packages via `dotnet-delice` +- npm packages via `license-checker` +- Reports blocked licenses (GPL-2.0-only, SSPL, etc.) + +--- + +### validate-migrations.sh + +Validate database migrations. + +**Usage:** +```bash +./validate-migrations.sh +``` + +**Checks:** +- Migration naming conventions +- Forward/rollback pairs +- Idempotency + +--- + +### validate-workflows.sh + +Validate Gitea Actions workflow YAML files. + +**Usage:** +```bash +./validate-workflows.sh +``` + +**Checks:** +- YAML syntax +- Required fields +- Action version pinning + +--- + +### verify-binaries.sh + +Verify binary integrity. + +**Usage:** +```bash +./verify-binaries.sh [checksum-file] +``` + +--- + +## Signing Scripts (`scripts/sign/`) + +### sign-signals.sh + +Sign Signals artifacts with Cosign. + +**Usage:** +```bash +./sign-signals.sh +``` + +**Environment Variables:** + +| Variable | Description | +|----------|-------------| +| `COSIGN_KEY_FILE` | Path to signing key | +| `COSIGN_PRIVATE_KEY_B64` | Base64-encoded private key | +| `COSIGN_PASSWORD` | Key password | +| `COSIGN_ALLOW_DEV_KEY` | Allow development key (`1`) | +| `OUT_DIR` | Output directory | + +**Key Resolution Order:** +1. `COSIGN_KEY_FILE` environment variable +2. `COSIGN_PRIVATE_KEY_B64` environment variable (decoded) +3. `tools/cosign/cosign.key` +4. `tools/cosign/cosign.dev.key` (if `COSIGN_ALLOW_DEV_KEY=1`) + +**Signed Artifacts:** +- `confidence_decay_config.yaml` +- `unknowns_scoring_manifest.json` +- `heuristics.catalog.json` + +**Output:** +``` +evidence-locker/signals/{date}/ +├── confidence_decay_config.sigstore.json +├── unknowns_scoring_manifest.sigstore.json +├── heuristics_catalog.sigstore.json +└── SHA256SUMS +``` + +--- + +### sign-policy.sh + +Sign policy artifacts. + +**Usage:** +```bash +./sign-policy.sh +``` + +--- + +### sign-authority-gaps.sh + +Sign authority gap attestations. + +**Usage:** +```bash +./sign-authority-gaps.sh +``` + +--- + +## Release Scripts (`scripts/release/`) + +### build_release.py + +Main release pipeline orchestration. + +**Usage:** +```bash +python build_release.py --channel stable --version 2026.04 +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `--channel` | Release channel (`stable`, `beta`, `nightly`) | +| `--version` | Version string | +| `--config` | Component config file | +| `--dry-run` | Don't push artifacts | + +**Dependencies:** +- docker (with buildx) +- cosign +- helm +- npm/node +- dotnet SDK + +--- + +### verify_release.py + +Post-release verification. + +**Usage:** +```bash +python verify_release.py --version 2026.04 +``` + +--- + +### bump-service-version.py + +Manage service versions in `Directory.Versions.props`. + +**Usage:** +```bash +# Bump version +python bump-service-version.py --service scanner --bump minor + +# Set explicit version +python bump-service-version.py --service scanner --version 2.0.0 + +# List versions +python bump-service-version.py --list +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `--service` | Service name (e.g., `scanner`, `authority`) | +| `--bump` | Bump type (`major`, `minor`, `patch`) | +| `--version` | Explicit version to set | +| `--list` | List all service versions | +| `--dry-run` | Don't write changes | + +--- + +### read-service-version.sh + +Read current service version. + +**Usage:** +```bash +./read-service-version.sh scanner +``` + +**Output:** +``` +1.2.3 +``` + +--- + +### generate-docker-tag.sh + +Generate Docker tag with datetime suffix. + +**Usage:** +```bash +./generate-docker-tag.sh 1.2.3 +``` + +**Output:** +``` +1.2.3+20250128143022 +``` + +--- + +### generate_changelog.py + +AI-assisted changelog generation. + +**Usage:** +```bash +python generate_changelog.py --version 2026.04 --codename Nova +``` + +**Environment Variables:** + +| Variable | Description | +|----------|-------------| +| `AI_API_KEY` | AI service API key | +| `AI_API_URL` | AI service endpoint (optional) | + +**Features:** +- Parses git commits since last release +- Categorizes by type (Breaking, Security, Features, Fixes) +- Groups by module +- AI-assisted summary generation +- Fallback to rule-based generation + +--- + +### generate_suite_docs.py + +Generate suite release documentation. + +**Usage:** +```bash +python generate_suite_docs.py --version 2026.04 --codename Nova +``` + +**Output:** +``` +docs/releases/2026.04/ +├── README.md +├── CHANGELOG.md +├── services.md +├── upgrade-guide.md +├── checksums.txt +└── manifest.yaml +``` + +--- + +### generate_compose.py + +Generate pinned Docker Compose files. + +**Usage:** +```bash +python generate_compose.py --version 2026.04 +``` + +**Output:** +- `docker-compose.yml` - Standard deployment +- `docker-compose.airgap.yml` - Air-gapped deployment + +--- + +### collect_versions.py + +Collect service versions from `Directory.Versions.props`. + +**Usage:** +```bash +python collect_versions.py --format json +python collect_versions.py --format yaml +python collect_versions.py --format markdown +python collect_versions.py --format env +``` + +--- + +### check_cli_parity.py + +Verify CLI version parity across platforms. + +**Usage:** +```bash +python check_cli_parity.py +``` + +--- + +## Evidence Scripts (`scripts/evidence/`) + +### upload-all-evidence.sh + +Upload all evidence bundles to Evidence Locker. + +**Usage:** +```bash +./upload-all-evidence.sh +``` + +--- + +### signals-upload-evidence.sh + +Upload Signals evidence. + +**Usage:** +```bash +./signals-upload-evidence.sh +``` + +--- + +### zastava-upload-evidence.sh + +Upload Zastava evidence. + +**Usage:** +```bash +./zastava-upload-evidence.sh +``` + +--- + +## Metrics Scripts (`scripts/metrics/`) + +### compute-reachability-metrics.sh + +Compute reachability analysis metrics. + +**Usage:** +```bash +./compute-reachability-metrics.sh +``` + +**Output Metrics:** +- Total functions analyzed +- Reachable functions +- Coverage percentage +- Analysis duration + +--- + +### compute-ttfs-metrics.sh + +Compute Time-to-First-Scan metrics. + +**Usage:** +```bash +./compute-ttfs-metrics.sh +``` + +--- + +### enforce-performance-slos.sh + +Enforce performance SLOs. + +**Usage:** +```bash +./enforce-performance-slos.sh +``` + +**Checked SLOs:** +- Build time < 30 minutes +- Test coverage > 80% +- TTFS < 60 seconds + +--- + +## Utility Scripts (`scripts/util/`) + +### cleanup-runner-space.sh + +Clean up runner disk space. + +**Usage:** +```bash +./cleanup-runner-space.sh +``` + +**Actions:** +- Remove Docker build cache +- Clean NuGet cache +- Remove old test results +- Prune unused images + +--- + +### dotnet-filter.sh + +Filter .NET projects for selective builds. + +**Usage:** +```bash +./dotnet-filter.sh --changed +./dotnet-filter.sh --module Scanner +``` + +--- + +### enable-openssl11-shim.sh + +Enable OpenSSL 1.1 compatibility shim. + +**Usage:** +```bash +./enable-openssl11-shim.sh +``` + +**Purpose:** +Required for certain cryptographic operations on newer Linux distributions that have removed OpenSSL 1.1. + +--- + +## Script Development Guidelines + +### Required Elements + +1. **Shebang:** + ```bash + #!/usr/bin/env bash + ``` + +2. **Strict Mode:** + ```bash + set -euo pipefail + ``` + +3. **Sprint Reference:** + ```bash + # DEVOPS-XXX-YY-ZZZ: Description + # Sprint: SPRINT_XXXX_XXXX_XXXX - Topic + ``` + +4. **Usage Documentation:** + ```bash + # Usage: + # ./script.sh [optional-arg] + ``` + +### Best Practices + +1. **Use environment variables with defaults:** + ```bash + CONFIG="${CONFIG:-Release}" + ``` + +2. **Validate required tools:** + ```bash + if ! command -v dotnet >/dev/null 2>&1; then + echo "dotnet CLI not found" >&2 + exit 69 + fi + ``` + +3. **Use absolute paths:** + ```bash + ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + ``` + +4. **Handle cleanup:** + ```bash + trap 'rm -f "$TMP_FILE"' EXIT + ``` + +5. **Use logging functions:** + ```bash + log_info() { echo "[INFO] $*"; } + log_error() { echo "[ERROR] $*" >&2; } + ``` diff --git a/.gitea/docs/troubleshooting.md b/.gitea/docs/troubleshooting.md new file mode 100644 index 000000000..d88799f0f --- /dev/null +++ b/.gitea/docs/troubleshooting.md @@ -0,0 +1,624 @@ +# CI/CD Troubleshooting Guide + +Common issues and solutions for StellaOps CI/CD infrastructure. + +## Quick Diagnostics + +### Check Workflow Status + +```bash +# View recent workflow runs +gh run list --limit 10 + +# View specific run logs +gh run view --log + +# Re-run failed workflow +gh run rerun +``` + +### Verify Local Environment + +```bash +# Check .NET SDK +dotnet --list-sdks + +# Check Docker +docker version +docker buildx version + +# Check Node.js +node --version +npm --version + +# Check required tools +which cosign syft helm +``` + +--- + +## Build Failures + +### NuGet Restore Failures + +**Symptom:** `error NU1301: Unable to load the service index` + +**Causes:** +1. Network connectivity issues +2. NuGet source unavailable +3. Invalid credentials + +**Solutions:** + +```bash +# Clear NuGet cache +dotnet nuget locals all --clear + +# Check NuGet sources +dotnet nuget list source + +# Restore with verbose logging +dotnet restore src/StellaOps.sln -v detailed +``` + +**In CI:** +```yaml +- name: Restore with retry + run: | + for i in {1..3}; do + dotnet restore src/StellaOps.sln && break + echo "Retry $i..." + sleep 30 + done +``` + +--- + +### SDK Version Mismatch + +**Symptom:** `error MSB4236: The SDK 'Microsoft.NET.Sdk' specified could not be found` + +**Solutions:** + +1. Check `global.json`: + ```bash + cat global.json + ``` + +2. Install correct SDK: + ```bash + # CI environment + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.100' + include-prerelease: true + ``` + +3. Override SDK version: + ```bash + # Remove global.json override + rm global.json + ``` + +--- + +### Docker Build Failures + +**Symptom:** `failed to solve: rpc error: code = Unknown` + +**Causes:** +1. Disk space exhausted +2. Layer cache corruption +3. Network timeout + +**Solutions:** + +```bash +# Clean Docker system +docker system prune -af +docker builder prune -af + +# Build without cache +docker build --no-cache -t myimage . + +# Increase buildx timeout +docker buildx create --driver-opt network=host --use +``` + +--- + +### Multi-arch Build Failures + +**Symptom:** `exec format error` or QEMU issues + +**Solutions:** + +```bash +# Install QEMU for cross-platform builds +docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +# Create new buildx builder +docker buildx create --name multiarch --driver docker-container --use +docker buildx inspect --bootstrap + +# Build for specific platforms +docker buildx build --platform linux/amd64 -t myimage . +``` + +--- + +## Test Failures + +### Testcontainers Issues + +**Symptom:** `Could not find a running Docker daemon` + +**Solutions:** + +1. Ensure Docker is running: + ```bash + docker info + ``` + +2. Set Testcontainers host: + ```bash + export TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal + # or for Linux + export TESTCONTAINERS_HOST_OVERRIDE=$(hostname -I | awk '{print $1}') + ``` + +3. Use Ryuk container for cleanup: + ```bash + export TESTCONTAINERS_RYUK_DISABLED=false + ``` + +4. CI configuration: + ```yaml + services: + dind: + image: docker:dind + privileged: true + ``` + +--- + +### PostgreSQL Test Failures + +**Symptom:** `FATAL: role "postgres" does not exist` + +**Solutions:** + +1. Check connection string: + ```bash + export STELLAOPS_TEST_POSTGRES_CONNECTION="Host=localhost;Database=test;Username=postgres;Password=postgres" + ``` + +2. Use Testcontainers PostgreSQL: + ```csharp + var container = new PostgreSqlBuilder() + .WithDatabase("test") + .WithUsername("postgres") + .WithPassword("postgres") + .Build(); + ``` + +3. Wait for PostgreSQL readiness: + ```bash + until pg_isready -h localhost -p 5432; do + sleep 1 + done + ``` + +--- + +### Test Timeouts + +**Symptom:** `Test exceeded timeout` + +**Solutions:** + +1. Increase timeout: + ```bash + dotnet test --blame-hang-timeout 10m + ``` + +2. Run tests in parallel with limited concurrency: + ```bash + dotnet test -maxcpucount:2 + ``` + +3. Identify slow tests: + ```bash + dotnet test --logger "console;verbosity=detailed" --logger "trx" + ``` + +--- + +### Determinism Test Failures + +**Symptom:** `Output mismatch: expected SHA256 differs` + +**Solutions:** + +1. Check for non-deterministic sources: + - Timestamps + - Random GUIDs + - Floating-point operations + - Dictionary ordering + +2. Run determinism comparison: + ```bash + .gitea/scripts/test/determinism-run.sh + diff out/scanner-determinism/run1.json out/scanner-determinism/run2.json + ``` + +3. Update golden fixtures: + ```bash + .gitea/scripts/test/run-fixtures-check.sh --update + ``` + +--- + +## Deployment Failures + +### SSH Connection Issues + +**Symptom:** `ssh: connect to host X.X.X.X port 22: Connection refused` + +**Solutions:** + +1. Verify SSH key: + ```bash + ssh-keygen -lf ~/.ssh/id_rsa.pub + ``` + +2. Test connection: + ```bash + ssh -vvv user@host + ``` + +3. Add host to known_hosts: + ```yaml + - name: Setup SSH + run: | + mkdir -p ~/.ssh + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts + ``` + +--- + +### Registry Push Failures + +**Symptom:** `unauthorized: authentication required` + +**Solutions:** + +1. Login to registry: + ```bash + docker login git.stella-ops.org -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD + ``` + +2. Check token permissions: + - `write:packages` scope required + - Token not expired + +3. Use credential helper: + ```yaml + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: git.stella-ops.org + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + ``` + +--- + +### Helm Deployment Failures + +**Symptom:** `Error: UPGRADE FAILED: cannot patch` + +**Solutions:** + +1. Check resource conflicts: + ```bash + kubectl get events -n stellaops --sort-by='.lastTimestamp' + ``` + +2. Force upgrade: + ```bash + helm upgrade --install --force stellaops ./devops/helm/stellaops + ``` + +3. Clean up stuck release: + ```bash + helm history stellaops + helm rollback stellaops + # or + kubectl delete secret -l name=stellaops,owner=helm + ``` + +--- + +## Workflow Issues + +### Workflow Not Triggering + +**Symptom:** Push/PR doesn't trigger workflow + +**Causes:** +1. Path filter not matching +2. Branch protection rules +3. YAML syntax error + +**Solutions:** + +1. Check path filters: + ```yaml + on: + push: + paths: + - 'src/**' # Check if files match + ``` + +2. Validate YAML: + ```bash + .gitea/scripts/validate/validate-workflows.sh + ``` + +3. Check branch rules: + - Verify workflow permissions + - Check protected branch settings + +--- + +### Concurrency Issues + +**Symptom:** Duplicate runs or stuck workflows + +**Solutions:** + +1. Add concurrency control: + ```yaml + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + ``` + +2. Cancel stale runs manually: + ```bash + gh run cancel + ``` + +--- + +### Artifact Upload/Download Failures + +**Symptom:** `Unable to find any artifacts` + +**Solutions:** + +1. Check artifact names match: + ```yaml + # Upload + - uses: actions/upload-artifact@v4 + with: + name: my-artifact # Must match + + # Download + - uses: actions/download-artifact@v4 + with: + name: my-artifact # Must match + ``` + +2. Check retention period: + ```yaml + - uses: actions/upload-artifact@v4 + with: + retention-days: 90 # Default is 90 + ``` + +3. Verify job dependencies: + ```yaml + download-job: + needs: [upload-job] # Must complete first + ``` + +--- + +## Runner Issues + +### Disk Space Exhausted + +**Symptom:** `No space left on device` + +**Solutions:** + +1. Run cleanup script: + ```bash + .gitea/scripts/util/cleanup-runner-space.sh + ``` + +2. Add cleanup step to workflow: + ```yaml + - name: Free disk space + run: | + docker system prune -af + rm -rf /tmp/* + df -h + ``` + +3. Use larger runner: + ```yaml + runs-on: ubuntu-latest-4xlarge + ``` + +--- + +### Out of Memory + +**Symptom:** `Killed` or `OOMKilled` + +**Solutions:** + +1. Limit parallel jobs: + ```yaml + strategy: + max-parallel: 2 + ``` + +2. Limit dotnet memory: + ```bash + export DOTNET_GCHeapHardLimit=0x40000000 # 1GB + ``` + +3. Use swap: + ```yaml + - name: Create swap + run: | + sudo fallocate -l 4G /swapfile + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + ``` + +--- + +### Runner Not Picking Up Jobs + +**Symptom:** Jobs stuck in `queued` state + +**Solutions:** + +1. Check runner status: + ```bash + # Self-hosted runner + ./run.sh --check + ``` + +2. Verify labels match: + ```yaml + runs-on: [self-hosted, linux, x64] # All labels must match + ``` + +3. Restart runner service: + ```bash + sudo systemctl restart actions.runner.*.service + ``` + +--- + +## Signing & Attestation Issues + +### Cosign Signing Failures + +**Symptom:** `error opening key: no such file` + +**Solutions:** + +1. Check key configuration: + ```bash + # From base64 secret + echo "$COSIGN_PRIVATE_KEY_B64" | base64 -d > cosign.key + + # Verify key + cosign public-key --key cosign.key + ``` + +2. Set password: + ```bash + export COSIGN_PASSWORD="${{ secrets.COSIGN_PASSWORD }}" + ``` + +3. Use keyless signing: + ```yaml + - name: Sign with keyless + env: + COSIGN_EXPERIMENTAL: 1 + run: cosign sign --yes $IMAGE + ``` + +--- + +### SBOM Generation Failures + +**Symptom:** `syft: command not found` + +**Solutions:** + +1. Install Syft: + ```bash + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin + ``` + +2. Use container: + ```yaml + - name: Generate SBOM + uses: anchore/sbom-action@v0 + with: + image: ${{ env.IMAGE }} + ``` + +--- + +## Debugging Tips + +### Enable Debug Logging + +```yaml +env: + ACTIONS_STEP_DEBUG: true + ACTIONS_RUNNER_DEBUG: true +``` + +### SSH into Runner + +```yaml +- name: Debug SSH + uses: mxschmitt/action-tmate@v3 + if: failure() +``` + +### Collect Diagnostic Info + +```yaml +- name: Diagnostics + if: failure() + run: | + echo "=== Environment ===" + env | sort + echo "=== Disk ===" + df -h + echo "=== Memory ===" + free -m + echo "=== Docker ===" + docker info + docker ps -a +``` + +### View Workflow Logs + +```bash +# Stream logs +gh run watch + +# Download logs +gh run download --name logs +``` + +--- + +## Getting Help + +1. **Check existing issues:** Search repository issues +2. **Review workflow history:** Look for similar failures +3. **Consult documentation:** `docs/` and `.gitea/docs/` +4. **Contact DevOps:** Create issue with label `ci-cd` + +### Information to Include + +- Workflow name and run ID +- Error message and stack trace +- Steps to reproduce +- Environment details (OS, SDK versions) +- Recent changes to affected code diff --git a/.gitea/scripts/release/bump-service-version.py b/.gitea/scripts/release/bump-service-version.py new file mode 100644 index 000000000..f4d6b6aad --- /dev/null +++ b/.gitea/scripts/release/bump-service-version.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +""" +bump-service-version.py - Bump service version in centralized version storage + +Sprint: CI/CD Enhancement - Per-Service Auto-Versioning +This script manages service versions stored in src/Directory.Versions.props +and devops/releases/service-versions.json. + +Usage: + python bump-service-version.py [options] + python bump-service-version.py authority patch + python bump-service-version.py scanner minor --dry-run + python bump-service-version.py cli major --commit + +Arguments: + service Service name (authority, attestor, concelier, scanner, etc.) + bump-type Version bump type: major, minor, patch, or explicit version (e.g., 2.0.0) + +Options: + --dry-run Show what would be changed without modifying files + --commit Commit changes to git after updating + --no-manifest Skip updating service-versions.json manifest + --git-sha SHA Git SHA to record in manifest (defaults to HEAD) + --docker-tag TAG Docker tag to record in manifest +""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional, Tuple + +# Repository paths +SCRIPT_DIR = Path(__file__).parent +REPO_ROOT = SCRIPT_DIR.parent.parent.parent +VERSIONS_FILE = REPO_ROOT / "src" / "Directory.Versions.props" +MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json" + +# Service name mapping (lowercase key -> property suffix) +SERVICE_MAP = { + "authority": "Authority", + "attestor": "Attestor", + "concelier": "Concelier", + "scanner": "Scanner", + "policy": "Policy", + "signer": "Signer", + "excititor": "Excititor", + "gateway": "Gateway", + "scheduler": "Scheduler", + "cli": "Cli", + "orchestrator": "Orchestrator", + "notify": "Notify", + "sbomservice": "SbomService", + "vexhub": "VexHub", + "evidencelocker": "EvidenceLocker", +} + + +def parse_version(version_str: str) -> Tuple[int, int, int]: + """Parse semantic version string into tuple.""" + match = re.match(r"^(\d+)\.(\d+)\.(\d+)$", version_str) + if not match: + raise ValueError(f"Invalid version format: {version_str}") + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + +def format_version(major: int, minor: int, patch: int) -> str: + """Format version tuple as string.""" + return f"{major}.{minor}.{patch}" + + +def bump_version(current: str, bump_type: str) -> str: + """Bump version according to bump type.""" + # Check if bump_type is an explicit version + if re.match(r"^\d+\.\d+\.\d+$", bump_type): + return bump_type + + major, minor, patch = parse_version(current) + + if bump_type == "major": + return format_version(major + 1, 0, 0) + elif bump_type == "minor": + return format_version(major, minor + 1, 0) + elif bump_type == "patch": + return format_version(major, minor, patch + 1) + else: + raise ValueError(f"Invalid bump type: {bump_type}") + + +def read_version_from_props(service_key: str) -> Optional[str]: + """Read current version from Directory.Versions.props.""" + if not VERSIONS_FILE.exists(): + return None + + property_name = f"StellaOps{SERVICE_MAP[service_key]}Version" + pattern = rf"<{property_name}>(\d+\.\d+\.\d+)" + + content = VERSIONS_FILE.read_text(encoding="utf-8") + match = re.search(pattern, content) + return match.group(1) if match else None + + +def update_version_in_props(service_key: str, new_version: str, dry_run: bool = False) -> bool: + """Update version in Directory.Versions.props.""" + if not VERSIONS_FILE.exists(): + print(f"Error: {VERSIONS_FILE} not found", file=sys.stderr) + return False + + property_name = f"StellaOps{SERVICE_MAP[service_key]}Version" + pattern = rf"(<{property_name}>)\d+\.\d+\.\d+()" + replacement = rf"\g<1>{new_version}\g<2>" + + content = VERSIONS_FILE.read_text(encoding="utf-8") + new_content, count = re.subn(pattern, replacement, content) + + if count == 0: + print(f"Error: Property {property_name} not found in {VERSIONS_FILE}", file=sys.stderr) + return False + + if dry_run: + print(f"[DRY-RUN] Would update {VERSIONS_FILE}") + print(f"[DRY-RUN] {property_name}: {new_version}") + else: + VERSIONS_FILE.write_text(new_content, encoding="utf-8") + print(f"Updated {VERSIONS_FILE}") + print(f" {property_name}: {new_version}") + + return True + + +def update_manifest( + service_key: str, + new_version: str, + git_sha: Optional[str] = None, + docker_tag: Optional[str] = None, + dry_run: bool = False, +) -> bool: + """Update service-versions.json manifest.""" + if not MANIFEST_FILE.exists(): + print(f"Warning: {MANIFEST_FILE} not found, skipping manifest update", file=sys.stderr) + return True + + try: + manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8")) + except json.JSONDecodeError as e: + print(f"Error parsing {MANIFEST_FILE}: {e}", file=sys.stderr) + return False + + if service_key not in manifest.get("services", {}): + print(f"Warning: Service '{service_key}' not found in manifest", file=sys.stderr) + return True + + # Update service entry + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + service = manifest["services"][service_key] + service["version"] = new_version + service["releasedAt"] = now + + if git_sha: + service["gitSha"] = git_sha + if docker_tag: + service["dockerTag"] = docker_tag + + # Update manifest timestamp + manifest["lastUpdated"] = now + + if dry_run: + print(f"[DRY-RUN] Would update {MANIFEST_FILE}") + print(f"[DRY-RUN] {service_key}.version: {new_version}") + if docker_tag: + print(f"[DRY-RUN] {service_key}.dockerTag: {docker_tag}") + else: + MANIFEST_FILE.write_text( + json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + print(f"Updated {MANIFEST_FILE}") + + return True + + +def get_git_sha() -> Optional[str]: + """Get current git HEAD SHA.""" + try: + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + cwd=REPO_ROOT, + check=True, + ) + return result.stdout.strip()[:12] # Short SHA + except subprocess.CalledProcessError: + return None + + +def commit_changes(service_key: str, old_version: str, new_version: str) -> bool: + """Commit version changes to git.""" + try: + # Stage the files + subprocess.run( + ["git", "add", str(VERSIONS_FILE), str(MANIFEST_FILE)], + cwd=REPO_ROOT, + check=True, + ) + + # Create commit + commit_msg = f"""chore({service_key}): bump version {old_version} -> {new_version} + +Automated version bump via bump-service-version.py + +Co-Authored-By: github-actions[bot] """ + + subprocess.run( + ["git", "commit", "-m", commit_msg], + cwd=REPO_ROOT, + check=True, + ) + print(f"Committed version bump: {old_version} -> {new_version}") + return True + except subprocess.CalledProcessError as e: + print(f"Error committing changes: {e}", file=sys.stderr) + return False + + +def generate_docker_tag(version: str) -> str: + """Generate Docker tag with datetime suffix: {version}+{YYYYMMDDHHmmss}.""" + timestamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") + return f"{version}+{timestamp}" + + +def main(): + parser = argparse.ArgumentParser( + description="Bump service version in centralized version storage", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + %(prog)s authority patch # Bump authority from 1.0.0 to 1.0.1 + %(prog)s scanner minor --dry-run # Preview bumping scanner minor version + %(prog)s cli 2.0.0 --commit # Set CLI to 2.0.0 and commit + %(prog)s gateway patch --docker-tag # Bump and generate docker tag + """, + ) + + parser.add_argument( + "service", + choices=list(SERVICE_MAP.keys()), + help="Service name to bump", + ) + parser.add_argument( + "bump_type", + help="Bump type: major, minor, patch, or explicit version (e.g., 2.0.0)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be changed without modifying files", + ) + parser.add_argument( + "--commit", + action="store_true", + help="Commit changes to git after updating", + ) + parser.add_argument( + "--no-manifest", + action="store_true", + help="Skip updating service-versions.json manifest", + ) + parser.add_argument( + "--git-sha", + help="Git SHA to record in manifest (defaults to HEAD)", + ) + parser.add_argument( + "--docker-tag", + nargs="?", + const="auto", + help="Docker tag to record in manifest (use 'auto' to generate)", + ) + parser.add_argument( + "--output-version", + action="store_true", + help="Output only the new version (for CI scripts)", + ) + parser.add_argument( + "--output-docker-tag", + action="store_true", + help="Output only the docker tag (for CI scripts)", + ) + + args = parser.parse_args() + + # Read current version + current_version = read_version_from_props(args.service) + if not current_version: + print(f"Error: Could not read current version for {args.service}", file=sys.stderr) + sys.exit(1) + + # Calculate new version + try: + new_version = bump_version(current_version, args.bump_type) + except ValueError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + # Generate docker tag if requested + docker_tag = None + if args.docker_tag: + docker_tag = generate_docker_tag(new_version) if args.docker_tag == "auto" else args.docker_tag + + # Output mode for CI scripts + if args.output_version: + print(new_version) + sys.exit(0) + if args.output_docker_tag: + print(docker_tag or generate_docker_tag(new_version)) + sys.exit(0) + + # Print summary + print(f"Service: {args.service}") + print(f"Current version: {current_version}") + print(f"New version: {new_version}") + if docker_tag: + print(f"Docker tag: {docker_tag}") + print() + + # Update version in props file + if not update_version_in_props(args.service, new_version, args.dry_run): + sys.exit(1) + + # Update manifest if not skipped + if not args.no_manifest: + git_sha = args.git_sha or get_git_sha() + if not update_manifest(args.service, new_version, git_sha, docker_tag, args.dry_run): + sys.exit(1) + + # Commit if requested + if args.commit and not args.dry_run: + if not commit_changes(args.service, current_version, new_version): + sys.exit(1) + + print() + print(f"Successfully bumped {args.service}: {current_version} -> {new_version}") + + +if __name__ == "__main__": + main() diff --git a/.gitea/scripts/release/collect_versions.py b/.gitea/scripts/release/collect_versions.py new file mode 100644 index 000000000..bea45936f --- /dev/null +++ b/.gitea/scripts/release/collect_versions.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +""" +collect_versions.py - Collect service versions for suite release + +Sprint: CI/CD Enhancement - Suite Release Pipeline +Gathers all service versions from Directory.Versions.props and service-versions.json. + +Usage: + python collect_versions.py [options] + python collect_versions.py --format json + python collect_versions.py --format yaml --output versions.yaml + +Options: + --format FMT Output format: json, yaml, markdown, env (default: json) + --output FILE Output file (defaults to stdout) + --include-unreleased Include services with no Docker tag + --registry URL Container registry URL +""" + +import argparse +import json +import os +import re +import sys +from dataclasses import dataclass, asdict +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional + +# Repository paths +SCRIPT_DIR = Path(__file__).parent +REPO_ROOT = SCRIPT_DIR.parent.parent.parent +VERSIONS_FILE = REPO_ROOT / "src" / "Directory.Versions.props" +MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json" + +# Default registry +DEFAULT_REGISTRY = "git.stella-ops.org/stella-ops.org" + + +@dataclass +class ServiceVersion: + name: str + version: str + docker_tag: Optional[str] = None + released_at: Optional[str] = None + git_sha: Optional[str] = None + image: Optional[str] = None + + +def read_versions_from_props() -> Dict[str, str]: + """Read versions from Directory.Versions.props.""" + if not VERSIONS_FILE.exists(): + print(f"Warning: {VERSIONS_FILE} not found", file=sys.stderr) + return {} + + content = VERSIONS_FILE.read_text(encoding="utf-8") + versions = {} + + # Pattern: X.Y.Z + pattern = r"(\d+\.\d+\.\d+)" + + for match in re.finditer(pattern, content): + service_name = match.group(1) + version = match.group(2) + versions[service_name.lower()] = version + + return versions + + +def read_manifest() -> Dict[str, dict]: + """Read service metadata from manifest file.""" + if not MANIFEST_FILE.exists(): + print(f"Warning: {MANIFEST_FILE} not found", file=sys.stderr) + return {} + + try: + manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8")) + return manifest.get("services", {}) + except json.JSONDecodeError as e: + print(f"Warning: Failed to parse {MANIFEST_FILE}: {e}", file=sys.stderr) + return {} + + +def collect_all_versions( + registry: str = DEFAULT_REGISTRY, + include_unreleased: bool = False, +) -> List[ServiceVersion]: + """Collect all service versions.""" + props_versions = read_versions_from_props() + manifest_services = read_manifest() + + services = [] + + # Merge data from both sources + all_service_keys = set(props_versions.keys()) | set(manifest_services.keys()) + + for key in sorted(all_service_keys): + version = props_versions.get(key, "0.0.0") + manifest = manifest_services.get(key, {}) + + docker_tag = manifest.get("dockerTag") + released_at = manifest.get("releasedAt") + git_sha = manifest.get("gitSha") + + # Skip unreleased if not requested + if not include_unreleased and not docker_tag: + continue + + # Build image reference + if docker_tag: + image = f"{registry}/{key}:{docker_tag}" + else: + image = f"{registry}/{key}:{version}" + + service = ServiceVersion( + name=manifest.get("name", key.title()), + version=version, + docker_tag=docker_tag, + released_at=released_at, + git_sha=git_sha, + image=image, + ) + + services.append(service) + + return services + + +def format_json(services: List[ServiceVersion]) -> str: + """Format as JSON.""" + data = { + "generatedAt": datetime.now(timezone.utc).isoformat(), + "services": [asdict(s) for s in services], + } + return json.dumps(data, indent=2, ensure_ascii=False) + + +def format_yaml(services: List[ServiceVersion]) -> str: + """Format as YAML.""" + lines = [ + "# Service Versions", + f"# Generated: {datetime.now(timezone.utc).isoformat()}", + "", + "services:", + ] + + for s in services: + lines.extend([ + f" {s.name.lower()}:", + f" name: {s.name}", + f" version: \"{s.version}\"", + ]) + if s.docker_tag: + lines.append(f" dockerTag: \"{s.docker_tag}\"") + if s.image: + lines.append(f" image: \"{s.image}\"") + if s.released_at: + lines.append(f" releasedAt: \"{s.released_at}\"") + if s.git_sha: + lines.append(f" gitSha: \"{s.git_sha}\"") + + return "\n".join(lines) + + +def format_markdown(services: List[ServiceVersion]) -> str: + """Format as Markdown table.""" + lines = [ + "# Service Versions", + "", + f"Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}", + "", + "| Service | Version | Docker Tag | Released |", + "|---------|---------|------------|----------|", + ] + + for s in services: + released = s.released_at[:10] if s.released_at else "-" + docker_tag = f"`{s.docker_tag}`" if s.docker_tag else "-" + lines.append(f"| {s.name} | {s.version} | {docker_tag} | {released} |") + + return "\n".join(lines) + + +def format_env(services: List[ServiceVersion]) -> str: + """Format as environment variables.""" + lines = [ + "# Service Versions as Environment Variables", + f"# Generated: {datetime.now(timezone.utc).isoformat()}", + "", + ] + + for s in services: + name_upper = s.name.upper().replace(" ", "_") + lines.append(f"STELLAOPS_{name_upper}_VERSION={s.version}") + if s.docker_tag: + lines.append(f"STELLAOPS_{name_upper}_DOCKER_TAG={s.docker_tag}") + if s.image: + lines.append(f"STELLAOPS_{name_upper}_IMAGE={s.image}") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Collect service versions for suite release", + ) + + parser.add_argument( + "--format", + choices=["json", "yaml", "markdown", "env"], + default="json", + help="Output format", + ) + parser.add_argument("--output", "-o", help="Output file") + parser.add_argument( + "--include-unreleased", + action="store_true", + help="Include services without Docker tags", + ) + parser.add_argument( + "--registry", + default=DEFAULT_REGISTRY, + help="Container registry URL", + ) + + args = parser.parse_args() + + # Collect versions + services = collect_all_versions( + registry=args.registry, + include_unreleased=args.include_unreleased, + ) + + if not services: + print("No services found", file=sys.stderr) + if not args.include_unreleased: + print("Hint: Use --include-unreleased to show all services", file=sys.stderr) + sys.exit(0) + + # Format output + formatters = { + "json": format_json, + "yaml": format_yaml, + "markdown": format_markdown, + "env": format_env, + } + + output = formatters[args.format](services) + + # Write output + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Versions written to: {args.output}", file=sys.stderr) + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/.gitea/scripts/release/generate-docker-tag.sh b/.gitea/scripts/release/generate-docker-tag.sh new file mode 100644 index 000000000..b653cce6c --- /dev/null +++ b/.gitea/scripts/release/generate-docker-tag.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# generate-docker-tag.sh - Generate Docker tag with datetime suffix +# +# Sprint: CI/CD Enhancement - Per-Service Auto-Versioning +# Generates Docker tags in format: {semver}+{YYYYMMDDHHmmss} +# +# Usage: +# ./generate-docker-tag.sh +# ./generate-docker-tag.sh --version +# ./generate-docker-tag.sh authority +# ./generate-docker-tag.sh --version 1.2.3 +# +# Output: +# Prints the Docker tag to stdout (e.g., "1.2.3+20250128143022") +# Exit code 0 on success, 1 on error + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +usage() { + cat << EOF +Usage: $(basename "$0") + +Generate Docker tag with datetime suffix. + +Format: {semver}+{YYYYMMDDHHmmss} +Example: 1.2.3+20250128143022 + +Arguments: + service Service name to read version from + --version VERSION Use explicit version instead of reading from file + +Options: + --timestamp TS Use explicit timestamp (YYYYMMDDHHmmss format) + --output-parts Output version and timestamp separately (JSON) + --help, -h Show this help message + +Examples: + $(basename "$0") authority # 1.0.0+20250128143022 + $(basename "$0") --version 2.0.0 # 2.0.0+20250128143022 + $(basename "$0") scanner --timestamp 20250101120000 + $(basename "$0") --version 1.0.0 --output-parts +EOF +} + +# Generate timestamp in UTC +generate_timestamp() { + date -u +"%Y%m%d%H%M%S" +} + +main() { + local version="" + local timestamp="" + local output_parts=false + local service="" + + while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + usage + exit 0 + ;; + --version) + version="$2" + shift 2 + ;; + --timestamp) + timestamp="$2" + shift 2 + ;; + --output-parts) + output_parts=true + shift + ;; + -*) + echo "Error: Unknown option: $1" >&2 + usage + exit 1 + ;; + *) + service="$1" + shift + ;; + esac + done + + # Get version from service if not explicitly provided + if [[ -z "$version" ]]; then + if [[ -z "$service" ]]; then + echo "Error: Either service name or --version must be provided" >&2 + usage + exit 1 + fi + + # Read version using read-service-version.sh + if [[ ! -x "${SCRIPT_DIR}/read-service-version.sh" ]]; then + echo "Error: read-service-version.sh not found or not executable" >&2 + exit 1 + fi + + version=$("${SCRIPT_DIR}/read-service-version.sh" "$service") + fi + + # Validate version format + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Invalid version format: $version (expected: X.Y.Z)" >&2 + exit 1 + fi + + # Generate timestamp if not provided + if [[ -z "$timestamp" ]]; then + timestamp=$(generate_timestamp) + fi + + # Validate timestamp format + if ! [[ "$timestamp" =~ ^[0-9]{14}$ ]]; then + echo "Error: Invalid timestamp format: $timestamp (expected: YYYYMMDDHHmmss)" >&2 + exit 1 + fi + + # Output + if [[ "$output_parts" == "true" ]]; then + echo "{\"version\":\"$version\",\"timestamp\":\"$timestamp\",\"tag\":\"${version}+${timestamp}\"}" + else + echo "${version}+${timestamp}" + fi +} + +main "$@" diff --git a/.gitea/scripts/release/generate_changelog.py b/.gitea/scripts/release/generate_changelog.py new file mode 100644 index 000000000..46de62ed6 --- /dev/null +++ b/.gitea/scripts/release/generate_changelog.py @@ -0,0 +1,448 @@ +#!/usr/bin/env python3 +""" +generate_changelog.py - AI-assisted changelog generation for suite releases + +Sprint: CI/CD Enhancement - Suite Release Pipeline +Generates changelogs from git commit history with optional AI enhancement. + +Usage: + python generate_changelog.py [options] + python generate_changelog.py 2026.04 --codename Nova + python generate_changelog.py 2026.04 --from-tag suite-2025.10 --ai + +Arguments: + version Suite version (YYYY.MM format) + +Options: + --codename NAME Release codename + --from-tag TAG Previous release tag (defaults to latest suite-* tag) + --to-ref REF End reference (defaults to HEAD) + --ai Use AI to enhance changelog descriptions + --output FILE Output file (defaults to stdout) + --format FMT Output format: markdown, json (default: markdown) +""" + +import argparse +import json +import os +import re +import subprocess +import sys +from dataclasses import dataclass, field +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional, Tuple +from collections import defaultdict + +# Repository paths +SCRIPT_DIR = Path(__file__).parent +REPO_ROOT = SCRIPT_DIR.parent.parent.parent + +# Module patterns for categorization +MODULE_PATTERNS = { + "Authority": r"src/Authority/", + "Attestor": r"src/Attestor/", + "Concelier": r"src/Concelier/", + "Scanner": r"src/Scanner/", + "Policy": r"src/Policy/", + "Signer": r"src/Signer/", + "Excititor": r"src/Excititor/", + "Gateway": r"src/Gateway/", + "Scheduler": r"src/Scheduler/", + "CLI": r"src/Cli/", + "Orchestrator": r"src/Orchestrator/", + "Notify": r"src/Notify/", + "Infrastructure": r"(devops/|\.gitea/|docs/)", + "Core": r"src/__Libraries/", +} + +# Commit type patterns (conventional commits) +COMMIT_TYPE_PATTERNS = { + "breaking": r"^(feat|fix|refactor)(\(.+\))?!:|BREAKING CHANGE:", + "security": r"^(security|fix)(\(.+\))?:|CVE-|vulnerability|exploit", + "feature": r"^feat(\(.+\))?:", + "fix": r"^fix(\(.+\))?:", + "performance": r"^perf(\(.+\))?:|performance|optimize", + "refactor": r"^refactor(\(.+\))?:", + "docs": r"^docs(\(.+\))?:", + "test": r"^test(\(.+\))?:", + "chore": r"^chore(\(.+\))?:|^ci(\(.+\))?:|^build(\(.+\))?:", +} + + +@dataclass +class Commit: + sha: str + short_sha: str + message: str + body: str + author: str + date: str + files: List[str] = field(default_factory=list) + type: str = "other" + module: str = "Other" + scope: str = "" + + +@dataclass +class ChangelogEntry: + description: str + commits: List[Commit] + module: str + type: str + + +def run_git(args: List[str], cwd: Path = REPO_ROOT) -> str: + """Run git command and return output.""" + result = subprocess.run( + ["git"] + args, + capture_output=True, + text=True, + cwd=cwd, + ) + if result.returncode != 0: + raise RuntimeError(f"Git command failed: {result.stderr}") + return result.stdout.strip() + + +def get_latest_suite_tag() -> Optional[str]: + """Get the most recent suite-* tag.""" + try: + output = run_git(["tag", "-l", "suite-*", "--sort=-creatordate"]) + tags = output.split("\n") + return tags[0] if tags and tags[0] else None + except RuntimeError: + return None + + +def get_commits_between(from_ref: str, to_ref: str = "HEAD") -> List[Commit]: + """Get commits between two refs.""" + # Format: sha|short_sha|subject|body|author|date + format_str = "%H|%h|%s|%b|%an|%aI" + separator = "---COMMIT_SEPARATOR---" + + try: + output = run_git([ + "log", + f"{from_ref}..{to_ref}", + f"--format={format_str}{separator}", + "--name-only", + ]) + except RuntimeError: + # If from_ref doesn't exist, get all commits up to to_ref + output = run_git([ + "log", + to_ref, + "-100", # Limit to last 100 commits + f"--format={format_str}{separator}", + "--name-only", + ]) + + commits = [] + entries = output.split(separator) + + for entry in entries: + entry = entry.strip() + if not entry: + continue + + lines = entry.split("\n") + if not lines: + continue + + # Parse commit info + parts = lines[0].split("|") + if len(parts) < 6: + continue + + # Get changed files (remaining lines after commit info) + files = [f.strip() for f in lines[1:] if f.strip()] + + commit = Commit( + sha=parts[0], + short_sha=parts[1], + message=parts[2], + body=parts[3] if len(parts) > 3 else "", + author=parts[4] if len(parts) > 4 else "", + date=parts[5] if len(parts) > 5 else "", + files=files, + ) + + # Categorize commit + commit.type = categorize_commit_type(commit.message) + commit.module = categorize_commit_module(commit.files, commit.message) + commit.scope = extract_scope(commit.message) + + commits.append(commit) + + return commits + + +def categorize_commit_type(message: str) -> str: + """Categorize commit by type based on message.""" + message_lower = message.lower() + + for commit_type, pattern in COMMIT_TYPE_PATTERNS.items(): + if re.search(pattern, message, re.IGNORECASE): + return commit_type + + return "other" + + +def categorize_commit_module(files: List[str], message: str) -> str: + """Categorize commit by module based on changed files.""" + module_counts: Dict[str, int] = defaultdict(int) + + for file in files: + for module, pattern in MODULE_PATTERNS.items(): + if re.search(pattern, file): + module_counts[module] += 1 + break + + if module_counts: + return max(module_counts, key=module_counts.get) + + # Try to extract from message scope + scope_match = re.match(r"^\w+\((\w+)\):", message) + if scope_match: + scope = scope_match.group(1).lower() + for module in MODULE_PATTERNS: + if module.lower() == scope: + return module + + return "Other" + + +def extract_scope(message: str) -> str: + """Extract scope from conventional commit message.""" + match = re.match(r"^\w+\(([^)]+)\):", message) + return match.group(1) if match else "" + + +def group_commits_by_type_and_module( + commits: List[Commit], +) -> Dict[str, Dict[str, List[Commit]]]: + """Group commits by type and module.""" + grouped: Dict[str, Dict[str, List[Commit]]] = defaultdict(lambda: defaultdict(list)) + + for commit in commits: + grouped[commit.type][commit.module].append(commit) + + return grouped + + +def generate_markdown_changelog( + version: str, + codename: str, + commits: List[Commit], + ai_enhanced: bool = False, +) -> str: + """Generate markdown changelog.""" + grouped = group_commits_by_type_and_module(commits) + + lines = [ + f"# Changelog - StellaOps {version} \"{codename}\"", + "", + f"Release Date: {datetime.now(timezone.utc).strftime('%Y-%m-%d')}", + "", + ] + + # Order of sections + section_order = [ + ("breaking", "Breaking Changes"), + ("security", "Security"), + ("feature", "Features"), + ("fix", "Bug Fixes"), + ("performance", "Performance"), + ("refactor", "Refactoring"), + ("docs", "Documentation"), + ("other", "Other Changes"), + ] + + for type_key, section_title in section_order: + if type_key not in grouped: + continue + + modules = grouped[type_key] + if not modules: + continue + + lines.append(f"## {section_title}") + lines.append("") + + # Sort modules alphabetically + for module in sorted(modules.keys()): + commits_in_module = modules[module] + if not commits_in_module: + continue + + lines.append(f"### {module}") + lines.append("") + + for commit in commits_in_module: + # Clean up message + msg = commit.message + # Remove conventional commit prefix for display + msg = re.sub(r"^\w+(\([^)]+\))?[!]?:\s*", "", msg) + + if ai_enhanced: + # Placeholder for AI-enhanced description + lines.append(f"- {msg} ([{commit.short_sha}])") + else: + lines.append(f"- {msg} (`{commit.short_sha}`)") + + lines.append("") + + # Add statistics + lines.extend([ + "---", + "", + "## Statistics", + "", + f"- **Total Commits:** {len(commits)}", + f"- **Contributors:** {len(set(c.author for c in commits))}", + f"- **Files Changed:** {len(set(f for c in commits for f in c.files))}", + "", + ]) + + return "\n".join(lines) + + +def generate_json_changelog( + version: str, + codename: str, + commits: List[Commit], +) -> str: + """Generate JSON changelog.""" + grouped = group_commits_by_type_and_module(commits) + + changelog = { + "version": version, + "codename": codename, + "date": datetime.now(timezone.utc).isoformat(), + "statistics": { + "totalCommits": len(commits), + "contributors": len(set(c.author for c in commits)), + "filesChanged": len(set(f for c in commits for f in c.files)), + }, + "sections": {}, + } + + for type_key, modules in grouped.items(): + if not modules: + continue + + changelog["sections"][type_key] = {} + + for module, module_commits in modules.items(): + changelog["sections"][type_key][module] = [ + { + "sha": c.short_sha, + "message": c.message, + "author": c.author, + "date": c.date, + } + for c in module_commits + ] + + return json.dumps(changelog, indent=2, ensure_ascii=False) + + +def enhance_with_ai(changelog: str, api_key: Optional[str] = None) -> str: + """Enhance changelog using AI (if available).""" + if not api_key: + api_key = os.environ.get("AI_API_KEY") + + if not api_key: + print("Warning: No AI API key provided, skipping AI enhancement", file=sys.stderr) + return changelog + + # This is a placeholder for AI integration + # In production, this would call Claude API or similar + prompt = f""" +You are a technical writer creating release notes for a security platform. +Improve the following changelog by: +1. Making descriptions more user-friendly +2. Highlighting important changes +3. Adding context where helpful +4. Keeping it concise + +Original changelog: +{changelog} + +Generate improved changelog in the same markdown format. +""" + + # For now, return the original changelog + # TODO: Implement actual AI API call + print("Note: AI enhancement is a placeholder, returning original changelog", file=sys.stderr) + return changelog + + +def main(): + parser = argparse.ArgumentParser( + description="Generate changelog from git history", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument("version", help="Suite version (YYYY.MM format)") + parser.add_argument("--codename", default="", help="Release codename") + parser.add_argument("--from-tag", help="Previous release tag") + parser.add_argument("--to-ref", default="HEAD", help="End reference") + parser.add_argument("--ai", action="store_true", help="Use AI enhancement") + parser.add_argument("--output", "-o", help="Output file") + parser.add_argument( + "--format", + choices=["markdown", "json"], + default="markdown", + help="Output format", + ) + + args = parser.parse_args() + + # Validate version format + if not re.match(r"^\d{4}\.(04|10)$", args.version): + print(f"Warning: Non-standard version format: {args.version}", file=sys.stderr) + + # Determine from tag + from_tag = args.from_tag + if not from_tag: + from_tag = get_latest_suite_tag() + if from_tag: + print(f"Using previous tag: {from_tag}", file=sys.stderr) + else: + print("No previous suite tag found, using last 100 commits", file=sys.stderr) + from_tag = "HEAD~100" + + # Get commits + print(f"Collecting commits from {from_tag} to {args.to_ref}...", file=sys.stderr) + commits = get_commits_between(from_tag, args.to_ref) + print(f"Found {len(commits)} commits", file=sys.stderr) + + if not commits: + print("No commits found in range", file=sys.stderr) + sys.exit(0) + + # Generate changelog + codename = args.codename or "TBD" + + if args.format == "json": + output = generate_json_changelog(args.version, codename, commits) + else: + output = generate_markdown_changelog( + args.version, codename, commits, ai_enhanced=args.ai + ) + + if args.ai: + output = enhance_with_ai(output) + + # Output + if args.output: + Path(args.output).write_text(output, encoding="utf-8") + print(f"Changelog written to: {args.output}", file=sys.stderr) + else: + print(output) + + +if __name__ == "__main__": + main() diff --git a/.gitea/scripts/release/generate_compose.py b/.gitea/scripts/release/generate_compose.py new file mode 100644 index 000000000..f4e9e5309 --- /dev/null +++ b/.gitea/scripts/release/generate_compose.py @@ -0,0 +1,373 @@ +#!/usr/bin/env python3 +""" +generate_compose.py - Generate pinned Docker Compose files for suite releases + +Sprint: CI/CD Enhancement - Suite Release Pipeline +Creates docker-compose.yml files with pinned image versions for releases. + +Usage: + python generate_compose.py [options] + python generate_compose.py 2026.04 Nova --output docker-compose.yml + python generate_compose.py 2026.04 Nova --airgap --output docker-compose.airgap.yml + +Arguments: + version Suite version (YYYY.MM format) + codename Release codename + +Options: + --output FILE Output file (default: stdout) + --airgap Generate air-gap variant + --registry URL Container registry URL + --include-deps Include infrastructure dependencies (postgres, valkey) +""" + +import argparse +import json +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional + +# Repository paths +SCRIPT_DIR = Path(__file__).parent +REPO_ROOT = SCRIPT_DIR.parent.parent.parent +MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json" + +# Default registry +DEFAULT_REGISTRY = "git.stella-ops.org/stella-ops.org" + +# Service definitions with port mappings and dependencies +SERVICE_DEFINITIONS = { + "authority": { + "ports": ["8080:8080"], + "depends_on": ["postgres"], + "environment": { + "AUTHORITY_DB_CONNECTION": "Host=postgres;Database=authority;Username=stellaops;Password=${POSTGRES_PASSWORD}", + }, + "healthcheck": { + "test": ["CMD", "curl", "-f", "http://localhost:8080/health"], + "interval": "30s", + "timeout": "10s", + "retries": 3, + }, + }, + "attestor": { + "ports": ["8081:8080"], + "depends_on": ["postgres", "authority"], + "environment": { + "ATTESTOR_DB_CONNECTION": "Host=postgres;Database=attestor;Username=stellaops;Password=${POSTGRES_PASSWORD}", + "ATTESTOR_AUTHORITY_URL": "http://authority:8080", + }, + }, + "concelier": { + "ports": ["8082:8080"], + "depends_on": ["postgres", "valkey"], + "environment": { + "CONCELIER_DB_CONNECTION": "Host=postgres;Database=concelier;Username=stellaops;Password=${POSTGRES_PASSWORD}", + "CONCELIER_CACHE_URL": "valkey:6379", + }, + }, + "scanner": { + "ports": ["8083:8080"], + "depends_on": ["postgres", "concelier"], + "environment": { + "SCANNER_DB_CONNECTION": "Host=postgres;Database=scanner;Username=stellaops;Password=${POSTGRES_PASSWORD}", + "SCANNER_CONCELIER_URL": "http://concelier:8080", + }, + "volumes": ["/var/run/docker.sock:/var/run/docker.sock:ro"], + }, + "policy": { + "ports": ["8084:8080"], + "depends_on": ["postgres"], + "environment": { + "POLICY_DB_CONNECTION": "Host=postgres;Database=policy;Username=stellaops;Password=${POSTGRES_PASSWORD}", + }, + }, + "signer": { + "ports": ["8085:8080"], + "depends_on": ["authority"], + "environment": { + "SIGNER_AUTHORITY_URL": "http://authority:8080", + }, + }, + "excititor": { + "ports": ["8086:8080"], + "depends_on": ["postgres", "concelier"], + "environment": { + "EXCITITOR_DB_CONNECTION": "Host=postgres;Database=excititor;Username=stellaops;Password=${POSTGRES_PASSWORD}", + }, + }, + "gateway": { + "ports": ["8000:8080"], + "depends_on": ["authority"], + "environment": { + "GATEWAY_AUTHORITY_URL": "http://authority:8080", + }, + }, + "scheduler": { + "ports": ["8087:8080"], + "depends_on": ["postgres", "valkey"], + "environment": { + "SCHEDULER_DB_CONNECTION": "Host=postgres;Database=scheduler;Username=stellaops;Password=${POSTGRES_PASSWORD}", + "SCHEDULER_QUEUE_URL": "valkey:6379", + }, + }, +} + +# Infrastructure services +INFRASTRUCTURE_SERVICES = { + "postgres": { + "image": "postgres:16-alpine", + "environment": { + "POSTGRES_USER": "stellaops", + "POSTGRES_PASSWORD": "${POSTGRES_PASSWORD:-stellaops}", + "POSTGRES_DB": "stellaops", + }, + "volumes": ["postgres_data:/var/lib/postgresql/data"], + "healthcheck": { + "test": ["CMD-SHELL", "pg_isready -U stellaops"], + "interval": "10s", + "timeout": "5s", + "retries": 5, + }, + }, + "valkey": { + "image": "valkey/valkey:8-alpine", + "volumes": ["valkey_data:/data"], + "healthcheck": { + "test": ["CMD", "valkey-cli", "ping"], + "interval": "10s", + "timeout": "5s", + "retries": 5, + }, + }, +} + + +def read_service_versions() -> Dict[str, dict]: + """Read service versions from manifest.""" + if not MANIFEST_FILE.exists(): + return {} + + try: + manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8")) + return manifest.get("services", {}) + except json.JSONDecodeError: + return {} + + +def generate_compose( + version: str, + codename: str, + registry: str, + services: Dict[str, dict], + airgap: bool = False, + include_deps: bool = True, +) -> str: + """Generate Docker Compose YAML.""" + now = datetime.now(timezone.utc) + + lines = [ + "# Docker Compose for StellaOps Suite", + f"# Version: {version} \"{codename}\"", + f"# Generated: {now.isoformat()}", + "#", + "# Usage:", + "# docker compose up -d", + "# docker compose logs -f", + "# docker compose down", + "#", + "# Environment variables:", + "# POSTGRES_PASSWORD - PostgreSQL password (default: stellaops)", + "#", + "", + "services:", + ] + + # Add infrastructure services if requested + if include_deps: + for name, config in INFRASTRUCTURE_SERVICES.items(): + lines.extend(generate_service_block(name, config, indent=2)) + + # Add StellaOps services + for svc_name, svc_def in SERVICE_DEFINITIONS.items(): + # Get version info from manifest + manifest_info = services.get(svc_name, {}) + docker_tag = manifest_info.get("dockerTag") or manifest_info.get("version", version) + + # Build image reference + if airgap: + image = f"localhost:5000/{svc_name}:{docker_tag}" + else: + image = f"{registry}/{svc_name}:{docker_tag}" + + # Build service config + config = { + "image": image, + "restart": "unless-stopped", + **svc_def, + } + + # Add release labels + config["labels"] = { + "com.stellaops.release.version": version, + "com.stellaops.release.codename": codename, + "com.stellaops.service.name": svc_name, + "com.stellaops.service.version": manifest_info.get("version", "1.0.0"), + } + + lines.extend(generate_service_block(svc_name, config, indent=2)) + + # Add volumes + lines.extend([ + "", + "volumes:", + ]) + + if include_deps: + lines.extend([ + " postgres_data:", + " driver: local", + " valkey_data:", + " driver: local", + ]) + + # Add networks + lines.extend([ + "", + "networks:", + " default:", + " name: stellaops", + " driver: bridge", + ]) + + return "\n".join(lines) + + +def generate_service_block(name: str, config: dict, indent: int = 2) -> List[str]: + """Generate YAML block for a service.""" + prefix = " " * indent + lines = [ + "", + f"{prefix}{name}:", + ] + + inner_prefix = " " * (indent + 2) + + # Image + if "image" in config: + lines.append(f"{inner_prefix}image: {config['image']}") + + # Container name + lines.append(f"{inner_prefix}container_name: stellaops-{name}") + + # Restart policy + if "restart" in config: + lines.append(f"{inner_prefix}restart: {config['restart']}") + + # Ports + if "ports" in config: + lines.append(f"{inner_prefix}ports:") + for port in config["ports"]: + lines.append(f"{inner_prefix} - \"{port}\"") + + # Volumes + if "volumes" in config: + lines.append(f"{inner_prefix}volumes:") + for vol in config["volumes"]: + lines.append(f"{inner_prefix} - {vol}") + + # Environment + if "environment" in config: + lines.append(f"{inner_prefix}environment:") + for key, value in config["environment"].items(): + lines.append(f"{inner_prefix} {key}: \"{value}\"") + + # Depends on + if "depends_on" in config: + lines.append(f"{inner_prefix}depends_on:") + for dep in config["depends_on"]: + lines.append(f"{inner_prefix} {dep}:") + lines.append(f"{inner_prefix} condition: service_healthy") + + # Health check + if "healthcheck" in config: + hc = config["healthcheck"] + lines.append(f"{inner_prefix}healthcheck:") + if "test" in hc: + test = hc["test"] + if isinstance(test, list): + lines.append(f"{inner_prefix} test: {json.dumps(test)}") + else: + lines.append(f"{inner_prefix} test: \"{test}\"") + for key in ["interval", "timeout", "retries", "start_period"]: + if key in hc: + lines.append(f"{inner_prefix} {key}: {hc[key]}") + + # Labels + if "labels" in config: + lines.append(f"{inner_prefix}labels:") + for key, value in config["labels"].items(): + lines.append(f"{inner_prefix} {key}: \"{value}\"") + + return lines + + +def main(): + parser = argparse.ArgumentParser( + description="Generate pinned Docker Compose files for suite releases", + ) + + parser.add_argument("version", help="Suite version (YYYY.MM format)") + parser.add_argument("codename", help="Release codename") + parser.add_argument("--output", "-o", help="Output file") + parser.add_argument( + "--airgap", + action="store_true", + help="Generate air-gap variant (localhost:5000 registry)", + ) + parser.add_argument( + "--registry", + default=DEFAULT_REGISTRY, + help="Container registry URL", + ) + parser.add_argument( + "--include-deps", + action="store_true", + default=True, + help="Include infrastructure dependencies", + ) + parser.add_argument( + "--no-deps", + action="store_true", + help="Exclude infrastructure dependencies", + ) + + args = parser.parse_args() + + # Read service versions + services = read_service_versions() + if not services: + print("Warning: No service versions found in manifest", file=sys.stderr) + + # Generate compose file + include_deps = args.include_deps and not args.no_deps + compose = generate_compose( + version=args.version, + codename=args.codename, + registry=args.registry, + services=services, + airgap=args.airgap, + include_deps=include_deps, + ) + + # Output + if args.output: + Path(args.output).write_text(compose, encoding="utf-8") + print(f"Docker Compose written to: {args.output}", file=sys.stderr) + else: + print(compose) + + +if __name__ == "__main__": + main() diff --git a/.gitea/scripts/release/generate_suite_docs.py b/.gitea/scripts/release/generate_suite_docs.py new file mode 100644 index 000000000..24e7f6ee1 --- /dev/null +++ b/.gitea/scripts/release/generate_suite_docs.py @@ -0,0 +1,477 @@ +#!/usr/bin/env python3 +""" +generate_suite_docs.py - Generate suite release documentation + +Sprint: CI/CD Enhancement - Suite Release Pipeline +Creates the docs/releases/YYYY.MM/ documentation structure. + +Usage: + python generate_suite_docs.py [options] + python generate_suite_docs.py 2026.04 Nova --channel lts + python generate_suite_docs.py 2026.10 Orion --changelog CHANGELOG.md + +Arguments: + version Suite version (YYYY.MM format) + codename Release codename + +Options: + --channel CH Release channel: edge, stable, lts + --changelog FILE Pre-generated changelog file + --output-dir DIR Output directory (default: docs/releases/YYYY.MM) + --registry URL Container registry URL + --previous VERSION Previous version for upgrade guide +""" + +import argparse +import json +import os +import re +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Dict, List, Optional + +# Repository paths +SCRIPT_DIR = Path(__file__).parent +REPO_ROOT = SCRIPT_DIR.parent.parent.parent +VERSIONS_FILE = REPO_ROOT / "src" / "Directory.Versions.props" +MANIFEST_FILE = REPO_ROOT / "devops" / "releases" / "service-versions.json" + +# Default registry +DEFAULT_REGISTRY = "git.stella-ops.org/stella-ops.org" + +# Support timeline +SUPPORT_TIMELINE = { + "edge": "3 months", + "stable": "9 months", + "lts": "5 years", +} + + +def get_git_sha() -> str: + """Get current git HEAD SHA.""" + try: + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + capture_output=True, + text=True, + cwd=REPO_ROOT, + check=True, + ) + return result.stdout.strip()[:12] + except subprocess.CalledProcessError: + return "unknown" + + +def read_service_versions() -> Dict[str, dict]: + """Read service versions from manifest.""" + if not MANIFEST_FILE.exists(): + return {} + + try: + manifest = json.loads(MANIFEST_FILE.read_text(encoding="utf-8")) + return manifest.get("services", {}) + except json.JSONDecodeError: + return {} + + +def generate_readme( + version: str, + codename: str, + channel: str, + registry: str, + services: Dict[str, dict], +) -> str: + """Generate README.md for the release.""" + now = datetime.now(timezone.utc) + support_period = SUPPORT_TIMELINE.get(channel, "unknown") + + lines = [ + f"# StellaOps {version} \"{codename}\"", + "", + f"**Release Date:** {now.strftime('%B %d, %Y')}", + f"**Channel:** {channel.upper()}", + f"**Support Period:** {support_period}", + "", + "## Overview", + "", + f"StellaOps {version} \"{codename}\" is a {'Long-Term Support (LTS)' if channel == 'lts' else channel} release ", + "of the StellaOps container security platform.", + "", + "## Quick Start", + "", + "### Docker Compose", + "", + "```bash", + f"curl -O https://git.stella-ops.org/stella-ops.org/releases/{version}/docker-compose.yml", + "docker compose up -d", + "```", + "", + "### Helm", + "", + "```bash", + f"helm repo add stellaops https://charts.stella-ops.org", + f"helm install stellaops stellaops/stellaops --version {version}", + "```", + "", + "## Included Services", + "", + "| Service | Version | Image |", + "|---------|---------|-------|", + ] + + for key, svc in sorted(services.items()): + name = svc.get("name", key.title()) + ver = svc.get("version", "1.0.0") + tag = svc.get("dockerTag", ver) + image = f"`{registry}/{key}:{tag}`" + lines.append(f"| {name} | {ver} | {image} |") + + lines.extend([ + "", + "## Documentation", + "", + "- [CHANGELOG.md](./CHANGELOG.md) - Detailed list of changes", + "- [services.md](./services.md) - Service version details", + "- [upgrade-guide.md](./upgrade-guide.md) - Upgrade instructions", + "- [docker-compose.yml](./docker-compose.yml) - Docker Compose configuration", + "", + "## Support", + "", + f"This release is supported until **{calculate_eol(now, channel)}**.", + "", + "For issues and feature requests, please visit:", + "https://git.stella-ops.org/stella-ops.org/git.stella-ops.org/issues", + "", + "---", + "", + f"Generated: {now.isoformat()}", + f"Git SHA: {get_git_sha()}", + ]) + + return "\n".join(lines) + + +def calculate_eol(release_date: datetime, channel: str) -> str: + """Calculate end-of-life date based on channel.""" + from dateutil.relativedelta import relativedelta + + periods = { + "edge": relativedelta(months=3), + "stable": relativedelta(months=9), + "lts": relativedelta(years=5), + } + + try: + eol = release_date + periods.get(channel, relativedelta(months=9)) + return eol.strftime("%B %Y") + except ImportError: + # Fallback without dateutil + return f"See {channel} support policy" + + +def generate_services_doc( + version: str, + codename: str, + registry: str, + services: Dict[str, dict], +) -> str: + """Generate services.md with detailed service information.""" + lines = [ + f"# Services - StellaOps {version} \"{codename}\"", + "", + "This document lists all services included in this release with their versions,", + "Docker images, and configuration details.", + "", + "## Service Matrix", + "", + "| Service | Version | Docker Tag | Released | Git SHA |", + "|---------|---------|------------|----------|---------|", + ] + + for key, svc in sorted(services.items()): + name = svc.get("name", key.title()) + ver = svc.get("version", "1.0.0") + tag = svc.get("dockerTag") or "-" + released = svc.get("releasedAt", "-") + if released != "-": + released = released[:10] + sha = svc.get("gitSha") or "-" + lines.append(f"| {name} | {ver} | `{tag}` | {released} | `{sha}` |") + + lines.extend([ + "", + "## Container Images", + "", + "All images are available from the StellaOps registry:", + "", + "```", + f"Registry: {registry}", + "```", + "", + "### Pull Commands", + "", + "```bash", + ]) + + for key, svc in sorted(services.items()): + tag = svc.get("dockerTag") or svc.get("version", "latest") + lines.append(f"docker pull {registry}/{key}:{tag}") + + lines.extend([ + "```", + "", + "## Service Descriptions", + "", + ]) + + service_descriptions = { + "authority": "Authentication and authorization service with OAuth/OIDC support", + "attestor": "in-toto/DSSE attestation generation and verification", + "concelier": "Vulnerability advisory ingestion and merge engine", + "scanner": "Container scanning with SBOM generation", + "policy": "Policy engine with K4 lattice logic", + "signer": "Cryptographic signing operations", + "excititor": "VEX document ingestion and export", + "gateway": "API gateway with routing and transport abstraction", + "scheduler": "Job scheduling and queue management", + "cli": "Command-line interface", + "orchestrator": "Workflow orchestration and task coordination", + "notify": "Notification delivery (Email, Slack, Teams, Webhooks)", + } + + for key, svc in sorted(services.items()): + name = svc.get("name", key.title()) + desc = service_descriptions.get(key, "StellaOps service") + lines.extend([ + f"### {name}", + "", + desc, + "", + f"- **Version:** {svc.get('version', '1.0.0')}", + f"- **Image:** `{registry}/{key}:{svc.get('dockerTag', 'latest')}`", + "", + ]) + + return "\n".join(lines) + + +def generate_upgrade_guide( + version: str, + codename: str, + previous_version: Optional[str], +) -> str: + """Generate upgrade-guide.md.""" + lines = [ + f"# Upgrade Guide - StellaOps {version} \"{codename}\"", + "", + ] + + if previous_version: + lines.extend([ + f"This guide covers upgrading from StellaOps {previous_version} to {version}.", + "", + ]) + else: + lines.extend([ + "This guide covers upgrading to this release from a previous version.", + "", + ]) + + lines.extend([ + "## Before You Begin", + "", + "1. **Backup your data** - Ensure all databases and configuration are backed up", + "2. **Review changelog** - Check [CHANGELOG.md](./CHANGELOG.md) for breaking changes", + "3. **Check compatibility** - Verify your environment meets the requirements", + "", + "## Upgrade Steps", + "", + "### Docker Compose", + "", + "```bash", + "# Pull new images", + "docker compose pull", + "", + "# Stop services", + "docker compose down", + "", + "# Start with new version", + "docker compose up -d", + "", + "# Verify health", + "docker compose ps", + "```", + "", + "### Helm", + "", + "```bash", + "# Update repository", + "helm repo update stellaops", + "", + "# Upgrade release", + f"helm upgrade stellaops stellaops/stellaops --version {version}", + "", + "# Verify status", + "helm status stellaops", + "```", + "", + "## Database Migrations", + "", + "Database migrations are applied automatically on service startup.", + "For manual migration control, set `AUTO_MIGRATE=false` and run:", + "", + "```bash", + "stellaops-cli db migrate", + "```", + "", + "## Configuration Changes", + "", + "Review the following configuration changes:", + "", + "| Setting | Previous | New | Notes |", + "|---------|----------|-----|-------|", + "| (No breaking changes) | - | - | - |", + "", + "## Rollback Procedure", + "", + "If issues occur, rollback to the previous version:", + "", + "### Docker Compose", + "", + "```bash", + "# Edit docker-compose.yml to use previous image tags", + "docker compose down", + "docker compose up -d", + "```", + "", + "### Helm", + "", + "```bash", + "helm rollback stellaops", + "```", + "", + "## Support", + "", + "For upgrade assistance, contact support or open an issue at:", + "https://git.stella-ops.org/stella-ops.org/git.stella-ops.org/issues", + ]) + + return "\n".join(lines) + + +def generate_manifest_yaml( + version: str, + codename: str, + channel: str, + services: Dict[str, dict], +) -> str: + """Generate manifest.yaml for the release.""" + now = datetime.now(timezone.utc) + + lines = [ + "apiVersion: stellaops.org/v1", + "kind: SuiteRelease", + "metadata:", + f" version: \"{version}\"", + f" codename: \"{codename}\"", + f" channel: \"{channel}\"", + f" date: \"{now.isoformat()}\"", + f" gitSha: \"{get_git_sha()}\"", + "spec:", + " services:", + ] + + for key, svc in sorted(services.items()): + lines.append(f" {key}:") + lines.append(f" version: \"{svc.get('version', '1.0.0')}\"") + if svc.get("dockerTag"): + lines.append(f" dockerTag: \"{svc['dockerTag']}\"") + if svc.get("gitSha"): + lines.append(f" gitSha: \"{svc['gitSha']}\"") + + return "\n".join(lines) + + +def main(): + parser = argparse.ArgumentParser( + description="Generate suite release documentation", + ) + + parser.add_argument("version", help="Suite version (YYYY.MM format)") + parser.add_argument("codename", help="Release codename") + parser.add_argument( + "--channel", + choices=["edge", "stable", "lts"], + default="stable", + help="Release channel", + ) + parser.add_argument("--changelog", help="Pre-generated changelog file") + parser.add_argument("--output-dir", help="Output directory") + parser.add_argument( + "--registry", + default=DEFAULT_REGISTRY, + help="Container registry URL", + ) + parser.add_argument("--previous", help="Previous version for upgrade guide") + + args = parser.parse_args() + + # Determine output directory + if args.output_dir: + output_dir = Path(args.output_dir) + else: + output_dir = REPO_ROOT / "docs" / "releases" / args.version + + output_dir.mkdir(parents=True, exist_ok=True) + print(f"Output directory: {output_dir}", file=sys.stderr) + + # Read service versions + services = read_service_versions() + if not services: + print("Warning: No service versions found in manifest", file=sys.stderr) + + # Generate README.md + readme = generate_readme( + args.version, args.codename, args.channel, args.registry, services + ) + (output_dir / "README.md").write_text(readme, encoding="utf-8") + print("Generated: README.md", file=sys.stderr) + + # Copy or generate CHANGELOG.md + if args.changelog and Path(args.changelog).exists(): + changelog = Path(args.changelog).read_text(encoding="utf-8") + else: + # Generate basic changelog + changelog = f"# Changelog - StellaOps {args.version} \"{args.codename}\"\n\n" + changelog += "See git history for detailed changes.\n" + (output_dir / "CHANGELOG.md").write_text(changelog, encoding="utf-8") + print("Generated: CHANGELOG.md", file=sys.stderr) + + # Generate services.md + services_doc = generate_services_doc( + args.version, args.codename, args.registry, services + ) + (output_dir / "services.md").write_text(services_doc, encoding="utf-8") + print("Generated: services.md", file=sys.stderr) + + # Generate upgrade-guide.md + upgrade_guide = generate_upgrade_guide( + args.version, args.codename, args.previous + ) + (output_dir / "upgrade-guide.md").write_text(upgrade_guide, encoding="utf-8") + print("Generated: upgrade-guide.md", file=sys.stderr) + + # Generate manifest.yaml + manifest = generate_manifest_yaml( + args.version, args.codename, args.channel, services + ) + (output_dir / "manifest.yaml").write_text(manifest, encoding="utf-8") + print("Generated: manifest.yaml", file=sys.stderr) + + print(f"\nSuite documentation generated in: {output_dir}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/.gitea/scripts/release/read-service-version.sh b/.gitea/scripts/release/read-service-version.sh new file mode 100644 index 000000000..8d4561db1 --- /dev/null +++ b/.gitea/scripts/release/read-service-version.sh @@ -0,0 +1,131 @@ +#!/bin/bash +# read-service-version.sh - Read service version from centralized storage +# +# Sprint: CI/CD Enhancement - Per-Service Auto-Versioning +# This script reads service versions from src/Directory.Versions.props +# +# Usage: +# ./read-service-version.sh +# ./read-service-version.sh authority +# ./read-service-version.sh --all +# +# Output: +# Prints the version string to stdout (e.g., "1.2.3") +# Exit code 0 on success, 1 on error + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +VERSIONS_FILE="${REPO_ROOT}/src/Directory.Versions.props" + +# Service name to property suffix mapping +declare -A SERVICE_MAP=( + ["authority"]="Authority" + ["attestor"]="Attestor" + ["concelier"]="Concelier" + ["scanner"]="Scanner" + ["policy"]="Policy" + ["signer"]="Signer" + ["excititor"]="Excititor" + ["gateway"]="Gateway" + ["scheduler"]="Scheduler" + ["cli"]="Cli" + ["orchestrator"]="Orchestrator" + ["notify"]="Notify" + ["sbomservice"]="SbomService" + ["vexhub"]="VexHub" + ["evidencelocker"]="EvidenceLocker" +) + +usage() { + cat << EOF +Usage: $(basename "$0") + +Read service version from centralized version storage. + +Arguments: + service Service name (authority, attestor, concelier, scanner, etc.) + --all Print all service versions in JSON format + +Services: + ${!SERVICE_MAP[*]} + +Examples: + $(basename "$0") authority # Output: 1.0.0 + $(basename "$0") scanner # Output: 1.2.3 + $(basename "$0") --all # Output: {"authority":"1.0.0",...} +EOF +} + +read_version() { + local service="$1" + local property_suffix="${SERVICE_MAP[$service]:-}" + + if [[ -z "$property_suffix" ]]; then + echo "Error: Unknown service '$service'" >&2 + echo "Valid services: ${!SERVICE_MAP[*]}" >&2 + return 1 + fi + + if [[ ! -f "$VERSIONS_FILE" ]]; then + echo "Error: Versions file not found: $VERSIONS_FILE" >&2 + return 1 + fi + + local property_name="StellaOps${property_suffix}Version" + local version + + version=$(grep -oP "<${property_name}>\K[0-9]+\.[0-9]+\.[0-9]+" "$VERSIONS_FILE" || true) + + if [[ -z "$version" ]]; then + echo "Error: Property '$property_name' not found in $VERSIONS_FILE" >&2 + return 1 + fi + + echo "$version" +} + +read_all_versions() { + if [[ ! -f "$VERSIONS_FILE" ]]; then + echo "Error: Versions file not found: $VERSIONS_FILE" >&2 + return 1 + fi + + echo -n "{" + local first=true + for service in "${!SERVICE_MAP[@]}"; do + local version + version=$(read_version "$service" 2>/dev/null || echo "") + if [[ -n "$version" ]]; then + if [[ "$first" != "true" ]]; then + echo -n "," + fi + echo -n "\"$service\":\"$version\"" + first=false + fi + done + echo "}" +} + +main() { + if [[ $# -eq 0 ]]; then + usage + exit 1 + fi + + case "$1" in + --help|-h) + usage + exit 0 + ;; + --all) + read_all_versions + ;; + *) + read_version "$1" + ;; + esac +} + +main "$@" diff --git a/.gitea/scripts/release/rollback.sh b/.gitea/scripts/release/rollback.sh new file mode 100644 index 000000000..d36e093ff --- /dev/null +++ b/.gitea/scripts/release/rollback.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Rollback Script +# Sprint: CI/CD Enhancement - Deployment Safety +# +# Purpose: Execute rollback to a previous version +# Usage: +# ./rollback.sh --environment --version --services --reason +# +# Exit codes: +# 0 - Rollback successful +# 1 - General error +# 2 - Invalid arguments +# 3 - Deployment failed +# 4 - Health check failed + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $*" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $*" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $*" >&2 +} + +log_step() { + echo -e "${BLUE}[STEP]${NC} $*" +} + +usage() { + cat << EOF +Usage: $(basename "$0") [OPTIONS] + +Execute rollback to a previous version. + +Options: + --environment Target environment (staging|production) + --version Target version to rollback to + --services JSON array of services to rollback + --reason Reason for rollback + --dry-run Show what would be done without executing + --help, -h Show this help message + +Examples: + $(basename "$0") --environment staging --version 1.2.3 --services '["scanner"]' --reason "Bug fix" + $(basename "$0") --environment production --version 1.2.0 --services '["authority","scanner"]' --reason "Hotfix rollback" + +Exit codes: + 0 Rollback successful + 1 General error + 2 Invalid arguments + 3 Deployment failed + 4 Health check failed +EOF +} + +# Default values +ENVIRONMENT="" +VERSION="" +SERVICES="" +REASON="" +DRY_RUN=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --environment) + ENVIRONMENT="$2" + shift 2 + ;; + --version) + VERSION="$2" + shift 2 + ;; + --services) + SERVICES="$2" + shift 2 + ;; + --reason) + REASON="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + usage + exit 2 + ;; + esac +done + +# Validate required arguments +if [[ -z "$ENVIRONMENT" ]] || [[ -z "$VERSION" ]] || [[ -z "$SERVICES" ]]; then + log_error "Missing required arguments" + usage + exit 2 +fi + +# Validate environment +if [[ "$ENVIRONMENT" != "staging" ]] && [[ "$ENVIRONMENT" != "production" ]]; then + log_error "Invalid environment: $ENVIRONMENT (must be staging or production)" + exit 2 +fi + +# Validate services JSON +if ! echo "$SERVICES" | jq empty 2>/dev/null; then + log_error "Invalid services JSON: $SERVICES" + exit 2 +fi + +log_info "Starting rollback process" +log_info " Environment: $ENVIRONMENT" +log_info " Version: $VERSION" +log_info " Services: $SERVICES" +log_info " Reason: $REASON" +log_info " Dry run: $DRY_RUN" + +# Record start time +START_TIME=$(date +%s) + +# Rollback each service +FAILED_SERVICES=() +SUCCESSFUL_SERVICES=() + +echo "$SERVICES" | jq -r '.[]' | while read -r service; do + log_step "Rolling back $service to $VERSION..." + + if [[ "$DRY_RUN" == "true" ]]; then + log_info " [DRY RUN] Would rollback $service" + continue + fi + + # Determine deployment method + HELM_RELEASE="stellaops-${service}" + NAMESPACE="stellaops-${ENVIRONMENT}" + + # Check if Helm release exists + if helm status "$HELM_RELEASE" -n "$NAMESPACE" >/dev/null 2>&1; then + log_info " Using Helm rollback for $service" + + # Get revision for target version + REVISION=$(helm history "$HELM_RELEASE" -n "$NAMESPACE" --output json | \ + jq -r --arg ver "$VERSION" '.[] | select(.app_version == $ver) | .revision' | tail -1) + + if [[ -n "$REVISION" ]]; then + if helm rollback "$HELM_RELEASE" "$REVISION" -n "$NAMESPACE" --wait --timeout 5m; then + log_info " Successfully rolled back $service to revision $REVISION" + SUCCESSFUL_SERVICES+=("$service") + else + log_error " Failed to rollback $service" + FAILED_SERVICES+=("$service") + fi + else + log_warn " No Helm revision found for version $VERSION" + log_info " Attempting deployment with specific version..." + + # Try to deploy specific version + IMAGE_TAG="${VERSION}" + VALUES_FILE="${REPO_ROOT}/devops/helm/values-${ENVIRONMENT}.yaml" + + if helm upgrade "$HELM_RELEASE" "${REPO_ROOT}/devops/helm/stellaops" \ + -n "$NAMESPACE" \ + --set "services.${service}.image.tag=${IMAGE_TAG}" \ + -f "$VALUES_FILE" \ + --wait --timeout 5m 2>/dev/null; then + log_info " Deployed $service with version $VERSION" + SUCCESSFUL_SERVICES+=("$service") + else + log_error " Failed to deploy $service with version $VERSION" + FAILED_SERVICES+=("$service") + fi + fi + else + log_warn " No Helm release found for $service" + log_info " Attempting kubectl rollout undo..." + + DEPLOYMENT="stellaops-${service}" + + if kubectl rollout undo deployment/"$DEPLOYMENT" -n "$NAMESPACE" 2>/dev/null; then + log_info " Rolled back deployment $DEPLOYMENT" + SUCCESSFUL_SERVICES+=("$service") + else + log_error " Failed to rollback deployment $DEPLOYMENT" + FAILED_SERVICES+=("$service") + fi + fi +done + +# Calculate duration +END_TIME=$(date +%s) +DURATION=$((END_TIME - START_TIME)) + +# Summary +echo "" +log_info "Rollback completed in ${DURATION}s" +log_info " Successful: ${#SUCCESSFUL_SERVICES[@]}" +log_info " Failed: ${#FAILED_SERVICES[@]}" + +if [[ ${#FAILED_SERVICES[@]} -gt 0 ]]; then + log_error "Failed services: ${FAILED_SERVICES[*]}" + exit 3 +fi + +log_info "Rollback successful" +exit 0 diff --git a/.gitea/scripts/test/run-test-category.sh b/.gitea/scripts/test/run-test-category.sh new file mode 100644 index 000000000..e387a6008 --- /dev/null +++ b/.gitea/scripts/test/run-test-category.sh @@ -0,0 +1,299 @@ +#!/usr/bin/env bash +# Test Category Runner +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Run tests for a specific category across all test projects +# Usage: ./run-test-category.sh [options] +# +# Options: +# --fail-on-empty Fail if no tests are found for the category +# --collect-coverage Collect code coverage data +# --verbose Show detailed output +# +# Exit Codes: +# 0 - Success (all tests passed or no tests found) +# 1 - One or more tests failed +# 2 - Invalid usage + +set -euo pipefail + +# Source shared libraries if available +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +if [[ -f "$REPO_ROOT/devops/scripts/lib/logging.sh" ]]; then + source "$REPO_ROOT/devops/scripts/lib/logging.sh" +else + # Minimal logging fallback + log_info() { echo "[INFO] $*"; } + log_error() { echo "[ERROR] $*" >&2; } + log_debug() { [[ -n "${DEBUG:-}" ]] && echo "[DEBUG] $*"; } + log_step() { echo "==> $*"; } +fi + +if [[ -f "$REPO_ROOT/devops/scripts/lib/exit-codes.sh" ]]; then + source "$REPO_ROOT/devops/scripts/lib/exit-codes.sh" +fi + +# ============================================================================= +# Constants +# ============================================================================= + +readonly FIND_PATTERN='\( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \)' +readonly EXCLUDE_PATHS='! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/bin/*" ! -path "*/obj/*"' +readonly EXCLUDE_FILES='! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj"' + +# ============================================================================= +# Functions +# ============================================================================= + +usage() { + cat < [options] + +Run tests for a specific test category across all test projects. + +Arguments: + category Test category (Unit, Architecture, Contract, Integration, + Security, Golden, Performance, Benchmark, AirGap, Chaos, + Determinism, Resilience, Observability) + +Options: + --fail-on-empty Exit with error if no tests found for the category + --collect-coverage Collect XPlat Code Coverage data + --verbose Show detailed test output + --results-dir DIR Custom results directory (default: ./TestResults/) + --help Show this help message + +Environment Variables: + DOTNET_VERSION .NET SDK version (default: uses installed version) + TZ Timezone (should be UTC for determinism) + +Examples: + $(basename "$0") Unit + $(basename "$0") Integration --collect-coverage + $(basename "$0") Performance --results-dir ./perf-results +EOF +} + +find_test_projects() { + local search_dir="${1:-src}" + + # Use eval to properly expand the find pattern + eval "find '$search_dir' $FIND_PATTERN -type f $EXCLUDE_PATHS $EXCLUDE_FILES" | sort +} + +sanitize_project_name() { + local proj="$1" + # Replace slashes with underscores, remove .csproj extension + echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj$||' +} + +run_tests() { + local category="$1" + local results_dir="$2" + local collect_coverage="$3" + local verbose="$4" + local fail_on_empty="$5" + + local passed=0 + local failed=0 + local skipped=0 + local no_tests=0 + + mkdir -p "$results_dir" + + local projects + projects=$(find_test_projects "$REPO_ROOT/src") + + if [[ -z "$projects" ]]; then + log_error "No test projects found" + return 1 + fi + + local project_count + project_count=$(echo "$projects" | grep -c '.csproj' || echo "0") + log_info "Found $project_count test projects" + + local category_lower + category_lower=$(echo "$category" | tr '[:upper:]' '[:lower:]') + + while IFS= read -r proj; do + [[ -z "$proj" ]] && continue + + local proj_name + proj_name=$(sanitize_project_name "$proj") + local trx_name="${proj_name}-${category_lower}.trx" + + # GitHub Actions grouping + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::group::Testing $proj ($category)" + else + log_step "Testing $proj ($category)" + fi + + # Build dotnet test command + local cmd="dotnet test \"$proj\"" + cmd+=" --filter \"Category=$category\"" + cmd+=" --configuration Release" + cmd+=" --logger \"trx;LogFileName=$trx_name\"" + cmd+=" --results-directory \"$results_dir\"" + + if [[ "$collect_coverage" == "true" ]]; then + cmd+=" --collect:\"XPlat Code Coverage\"" + fi + + if [[ "$verbose" == "true" ]]; then + cmd+=" --verbosity normal" + else + cmd+=" --verbosity minimal" + fi + + # Execute tests + local exit_code=0 + eval "$cmd" 2>&1 || exit_code=$? + + if [[ $exit_code -eq 0 ]]; then + # Check if TRX was created (tests actually ran) + if [[ -f "$results_dir/$trx_name" ]]; then + ((passed++)) + log_info "PASS: $proj" + else + ((no_tests++)) + log_debug "SKIP: $proj (no $category tests)" + fi + else + # Check if failure was due to no tests matching the filter + if [[ -f "$results_dir/$trx_name" ]]; then + ((failed++)) + log_error "FAIL: $proj" + else + ((no_tests++)) + log_debug "SKIP: $proj (no $category tests or build error)" + fi + fi + + # Close GitHub Actions group + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::endgroup::" + fi + + done <<< "$projects" + + # Generate summary + log_info "" + log_info "==========================================" + log_info "$category Test Summary" + log_info "==========================================" + log_info "Passed: $passed" + log_info "Failed: $failed" + log_info "No Tests: $no_tests" + log_info "Total: $project_count" + log_info "==========================================" + + # GitHub Actions summary + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + { + echo "## $category Test Summary" + echo "" + echo "| Metric | Count |" + echo "|--------|-------|" + echo "| Passed | $passed |" + echo "| Failed | $failed |" + echo "| No Tests | $no_tests |" + echo "| Total Projects | $project_count |" + } >> "$GITHUB_STEP_SUMMARY" + fi + + # Determine exit code + if [[ $failed -gt 0 ]]; then + return 1 + fi + + if [[ "$fail_on_empty" == "true" ]] && [[ $passed -eq 0 ]]; then + log_error "No tests found for category: $category" + return 1 + fi + + return 0 +} + +# ============================================================================= +# Main +# ============================================================================= + +main() { + local category="" + local results_dir="" + local collect_coverage="false" + local verbose="false" + local fail_on_empty="false" + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + --help|-h) + usage + exit 0 + ;; + --fail-on-empty) + fail_on_empty="true" + shift + ;; + --collect-coverage) + collect_coverage="true" + shift + ;; + --verbose|-v) + verbose="true" + shift + ;; + --results-dir) + results_dir="$2" + shift 2 + ;; + -*) + log_error "Unknown option: $1" + usage + exit 2 + ;; + *) + if [[ -z "$category" ]]; then + category="$1" + else + log_error "Unexpected argument: $1" + usage + exit 2 + fi + shift + ;; + esac + done + + # Validate category + if [[ -z "$category" ]]; then + log_error "Category is required" + usage + exit 2 + fi + + # Validate category name + local valid_categories="Unit Architecture Contract Integration Security Golden Performance Benchmark AirGap Chaos Determinism Resilience Observability" + if ! echo "$valid_categories" | grep -qw "$category"; then + log_error "Invalid category: $category" + log_error "Valid categories: $valid_categories" + exit 2 + fi + + # Set default results directory + if [[ -z "$results_dir" ]]; then + results_dir="./TestResults/$category" + fi + + log_info "Running $category tests..." + log_info "Results directory: $results_dir" + + run_tests "$category" "$results_dir" "$collect_coverage" "$verbose" "$fail_on_empty" +} + +main "$@" diff --git a/.gitea/scripts/validate/validate-migrations.sh b/.gitea/scripts/validate/validate-migrations.sh new file mode 100644 index 000000000..d17e33ad5 --- /dev/null +++ b/.gitea/scripts/validate/validate-migrations.sh @@ -0,0 +1,260 @@ +#!/usr/bin/env bash +# Migration Validation Script +# Validates migration naming conventions, detects duplicates, and checks for issues. +# +# Usage: +# ./validate-migrations.sh [--strict] [--fix-scanner] +# +# Options: +# --strict Exit with error on any warning +# --fix-scanner Generate rename commands for Scanner duplicates + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +STRICT_MODE=false +FIX_SCANNER=false +EXIT_CODE=0 + +# Parse arguments +for arg in "$@"; do + case $arg in + --strict) + STRICT_MODE=true + shift + ;; + --fix-scanner) + FIX_SCANNER=true + shift + ;; + esac +done + +echo "=== Migration Validation ===" +echo "Repository: $REPO_ROOT" +echo "" + +# Colors for output +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +# Track issues +ERRORS=() +WARNINGS=() + +# Function to check for duplicates in a directory +check_duplicates() { + local dir="$1" + local module="$2" + + if [ ! -d "$dir" ]; then + return + fi + + # Extract numeric prefixes and find duplicates + local duplicates + duplicates=$(find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | \ + sed -E 's/^([0-9]+)_.*/\1/' | \ + sort | uniq -d) + + if [ -n "$duplicates" ]; then + for prefix in $duplicates; do + local files + files=$(find "$dir" -maxdepth 1 -name "${prefix}_*.sql" -printf "%f\n" | tr '\n' ', ' | sed 's/,$//') + ERRORS+=("[$module] Duplicate prefix $prefix: $files") + done + fi +} + +# Function to check naming convention +check_naming() { + local dir="$1" + local module="$2" + + if [ ! -d "$dir" ]; then + return + fi + + find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | while read -r file; do + # Check standard pattern: NNN_description.sql + if [[ "$file" =~ ^[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then + continue # Valid standard + fi + # Check seed pattern: SNNN_description.sql + if [[ "$file" =~ ^S[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then + continue # Valid seed + fi + # Check data migration pattern: DMNNN_description.sql + if [[ "$file" =~ ^DM[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then + continue # Valid data migration + fi + # Check for Flyway-style + if [[ "$file" =~ ^V[0-9]+.*\.sql$ ]]; then + WARNINGS+=("[$module] Flyway-style naming: $file (consider NNN_description.sql)") + continue + fi + # Check for EF Core timestamp style + if [[ "$file" =~ ^[0-9]{14,}_.*\.sql$ ]]; then + WARNINGS+=("[$module] EF Core timestamp naming: $file (consider NNN_description.sql)") + continue + fi + # Check for 4-digit prefix + if [[ "$file" =~ ^[0-9]{4}_.*\.sql$ ]]; then + WARNINGS+=("[$module] 4-digit prefix: $file (standard is 3-digit NNN_description.sql)") + continue + fi + # Non-standard + WARNINGS+=("[$module] Non-standard naming: $file") + done +} + +# Function to check for dangerous operations in startup migrations +check_dangerous_ops() { + local dir="$1" + local module="$2" + + if [ ! -d "$dir" ]; then + return + fi + + find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | while read -r file; do + local filepath="$dir/$file" + local prefix + prefix=$(echo "$file" | sed -E 's/^([0-9]+)_.*/\1/') + + # Only check startup migrations (001-099) + if [[ "$prefix" =~ ^0[0-9]{2}$ ]] && [ "$prefix" -lt 100 ]; then + # Check for DROP TABLE without IF EXISTS + if grep -qE "DROP\s+TABLE\s+(?!IF\s+EXISTS)" "$filepath" 2>/dev/null; then + ERRORS+=("[$module] $file: DROP TABLE without IF EXISTS in startup migration") + fi + + # Check for DROP COLUMN (breaking change in startup) + if grep -qiE "ALTER\s+TABLE.*DROP\s+COLUMN" "$filepath" 2>/dev/null; then + ERRORS+=("[$module] $file: DROP COLUMN in startup migration (should be release migration 100+)") + fi + + # Check for TRUNCATE + if grep -qiE "^\s*TRUNCATE" "$filepath" 2>/dev/null; then + ERRORS+=("[$module] $file: TRUNCATE in startup migration") + fi + fi + done +} + +# Scan all module migration directories +echo "Scanning migration directories..." +echo "" + +# Define module migration paths +declare -A MIGRATION_PATHS +MIGRATION_PATHS=( + ["Authority"]="src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/Migrations" + ["Concelier"]="src/Concelier/__Libraries/StellaOps.Concelier.Storage.Postgres/Migrations" + ["Excititor"]="src/Excititor/__Libraries/StellaOps.Excititor.Storage.Postgres/Migrations" + ["Policy"]="src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/Migrations" + ["Scheduler"]="src/Scheduler/__Libraries/StellaOps.Scheduler.Storage.Postgres/Migrations" + ["Notify"]="src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations" + ["Scanner"]="src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations" + ["Scanner.Triage"]="src/Scanner/__Libraries/StellaOps.Scanner.Triage/Migrations" + ["Attestor"]="src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations" + ["Signer"]="src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations" + ["Signals"]="src/Signals/StellaOps.Signals.Storage.Postgres/Migrations" + ["EvidenceLocker"]="src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Infrastructure/Db/Migrations" + ["ExportCenter"]="src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Infrastructure/Db/Migrations" + ["IssuerDirectory"]="src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres/Migrations" + ["Orchestrator"]="src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Infrastructure/migrations" + ["TimelineIndexer"]="src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Infrastructure/Db/Migrations" + ["BinaryIndex"]="src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations" + ["Unknowns"]="src/Unknowns/__Libraries/StellaOps.Unknowns.Storage.Postgres/Migrations" + ["VexHub"]="src/VexHub/__Libraries/StellaOps.VexHub.Storage.Postgres/Migrations" +) + +for module in "${!MIGRATION_PATHS[@]}"; do + path="$REPO_ROOT/${MIGRATION_PATHS[$module]}" + if [ -d "$path" ]; then + echo "Checking: $module" + check_duplicates "$path" "$module" + check_naming "$path" "$module" + check_dangerous_ops "$path" "$module" + fi +done + +echo "" + +# Report errors +if [ ${#ERRORS[@]} -gt 0 ]; then + echo -e "${RED}=== ERRORS (${#ERRORS[@]}) ===${NC}" + for error in "${ERRORS[@]}"; do + echo -e "${RED} ✗ $error${NC}" + done + EXIT_CODE=1 + echo "" +fi + +# Report warnings +if [ ${#WARNINGS[@]} -gt 0 ]; then + echo -e "${YELLOW}=== WARNINGS (${#WARNINGS[@]}) ===${NC}" + for warning in "${WARNINGS[@]}"; do + echo -e "${YELLOW} ⚠ $warning${NC}" + done + if [ "$STRICT_MODE" = true ]; then + EXIT_CODE=1 + fi + echo "" +fi + +# Scanner fix suggestions +if [ "$FIX_SCANNER" = true ]; then + echo "=== Scanner Migration Rename Suggestions ===" + echo "# Run these commands to fix Scanner duplicate migrations:" + echo "" + + SCANNER_DIR="$REPO_ROOT/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations" + if [ -d "$SCANNER_DIR" ]; then + # Map old names to new sequential numbers + cat << 'EOF' +# Before running: backup the schema_migrations table! +# After renaming: update schema_migrations.migration_name to match new names + +cd src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations + +# Fix duplicate 009 prefixes +git mv 009_call_graph_tables.sql 020_call_graph_tables.sql +git mv 009_smart_diff_tables_search_path.sql 021_smart_diff_tables_search_path.sql + +# Fix duplicate 010 prefixes +git mv 010_reachability_drift_tables.sql 022_reachability_drift_tables.sql +git mv 010_scanner_api_ingestion.sql 023_scanner_api_ingestion.sql +git mv 010_smart_diff_priority_score_widen.sql 024_smart_diff_priority_score_widen.sql + +# Fix duplicate 014 prefixes +git mv 014_epss_triage_columns.sql 025_epss_triage_columns.sql +git mv 014_vuln_surfaces.sql 026_vuln_surfaces.sql + +# Renumber subsequent migrations +git mv 011_epss_raw_layer.sql 027_epss_raw_layer.sql +git mv 012_epss_signal_layer.sql 028_epss_signal_layer.sql +git mv 013_witness_storage.sql 029_witness_storage.sql +git mv 015_vuln_surface_triggers_update.sql 030_vuln_surface_triggers_update.sql +git mv 016_reach_cache.sql 031_reach_cache.sql +git mv 017_idempotency_keys.sql 032_idempotency_keys.sql +git mv 018_binary_evidence.sql 033_binary_evidence.sql +git mv 019_func_proof_tables.sql 034_func_proof_tables.sql +EOF + fi + echo "" +fi + +# Summary +if [ $EXIT_CODE -eq 0 ]; then + echo -e "${GREEN}=== VALIDATION PASSED ===${NC}" +else + echo -e "${RED}=== VALIDATION FAILED ===${NC}" +fi + +exit $EXIT_CODE diff --git a/.gitea/workflows/container-scan.yml b/.gitea/workflows/container-scan.yml new file mode 100644 index 000000000..884eadeef --- /dev/null +++ b/.gitea/workflows/container-scan.yml @@ -0,0 +1,227 @@ +# Container Security Scanning Workflow +# Sprint: CI/CD Enhancement - Security Scanning +# +# Purpose: Scan container images for vulnerabilities beyond SBOM generation +# Triggers: Dockerfile changes, scheduled daily, manual dispatch +# +# Tool: PLACEHOLDER - Choose one: Trivy, Grype, or Snyk + +name: Container Security Scan + +on: + push: + paths: + - '**/Dockerfile' + - '**/Dockerfile.*' + - 'devops/docker/**' + pull_request: + paths: + - '**/Dockerfile' + - '**/Dockerfile.*' + - 'devops/docker/**' + schedule: + # Run daily at 4 AM UTC + - cron: '0 4 * * *' + workflow_dispatch: + inputs: + severity_threshold: + description: 'Minimum severity to fail' + required: false + type: choice + options: + - CRITICAL + - HIGH + - MEDIUM + - LOW + default: HIGH + image: + description: 'Specific image to scan (optional)' + required: false + type: string + +env: + SEVERITY_THRESHOLD: ${{ github.event.inputs.severity_threshold || 'HIGH' }} + +jobs: + discover-images: + name: Discover Container Images + runs-on: ubuntu-latest + outputs: + images: ${{ steps.discover.outputs.images }} + count: ${{ steps.discover.outputs.count }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Discover Dockerfiles + id: discover + run: | + # Find all Dockerfiles + DOCKERFILES=$(find . -name "Dockerfile" -o -name "Dockerfile.*" | grep -v node_modules | grep -v bin | grep -v obj || true) + + # Build image list + IMAGES='[]' + COUNT=0 + + while IFS= read -r dockerfile; do + if [[ -n "$dockerfile" ]]; then + DIR=$(dirname "$dockerfile") + NAME=$(basename "$DIR" | tr '[:upper:]' '[:lower:]' | tr '.' '-') + + # Get image name from directory structure + if [[ "$DIR" == *"devops/docker"* ]]; then + NAME=$(echo "$dockerfile" | sed 's|.*devops/docker/||' | sed 's|/Dockerfile.*||' | tr '/' '-') + fi + + IMAGES=$(echo "$IMAGES" | jq --arg name "$NAME" --arg path "$dockerfile" '. + [{"name": $name, "dockerfile": $path}]') + COUNT=$((COUNT + 1)) + fi + done <<< "$DOCKERFILES" + + echo "Found $COUNT Dockerfile(s)" + echo "images=$(echo "$IMAGES" | jq -c .)" >> $GITHUB_OUTPUT + echo "count=$COUNT" >> $GITHUB_OUTPUT + + scan-images: + name: Scan ${{ matrix.image.name }} + runs-on: ubuntu-latest + needs: [discover-images] + if: needs.discover-images.outputs.count != '0' + strategy: + fail-fast: false + matrix: + image: ${{ fromJson(needs.discover-images.outputs.images) }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build image for scanning + id: build + run: | + IMAGE_TAG="scan-${{ matrix.image.name }}:${{ github.sha }}" + DOCKERFILE="${{ matrix.image.dockerfile }}" + CONTEXT=$(dirname "$DOCKERFILE") + + echo "Building $IMAGE_TAG from $DOCKERFILE..." + docker build -t "$IMAGE_TAG" -f "$DOCKERFILE" "$CONTEXT" || { + echo "::warning::Failed to build $IMAGE_TAG - skipping scan" + echo "skip=true" >> $GITHUB_OUTPUT + exit 0 + } + + echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT + echo "skip=false" >> $GITHUB_OUTPUT + + # PLACEHOLDER: Choose your container scanner + # Option 1: Trivy (recommended - comprehensive, free) + # Option 2: Grype (Anchore - good integration with Syft SBOMs) + # Option 3: Snyk (commercial, comprehensive) + + - name: Trivy Vulnerability Scan + if: steps.build.outputs.skip != 'true' + id: trivy + # Uncomment when ready to use Trivy: + # uses: aquasecurity/trivy-action@master + # with: + # image-ref: ${{ steps.build.outputs.image_tag }} + # format: 'sarif' + # output: 'trivy-${{ matrix.image.name }}.sarif' + # severity: ${{ env.SEVERITY_THRESHOLD }},CRITICAL + # exit-code: '1' + run: | + echo "::notice::Container scanning placeholder - configure scanner below" + echo "" + echo "Image: ${{ steps.build.outputs.image_tag }}" + echo "Severity threshold: ${{ env.SEVERITY_THRESHOLD }}" + echo "" + echo "Available scanners:" + echo " 1. Trivy: aquasecurity/trivy-action@master" + echo " 2. Grype: anchore/scan-action@v3" + echo " 3. Snyk: snyk/actions/docker@master" + + # Create placeholder report + mkdir -p scan-results + echo '{"placeholder": true, "image": "${{ matrix.image.name }}"}' > scan-results/scan-${{ matrix.image.name }}.json + + # Alternative: Grype (works well with existing Syft SBOM workflow) + # - name: Grype Vulnerability Scan + # if: steps.build.outputs.skip != 'true' + # uses: anchore/scan-action@v3 + # with: + # image: ${{ steps.build.outputs.image_tag }} + # severity-cutoff: ${{ env.SEVERITY_THRESHOLD }} + # fail-build: true + + # Alternative: Snyk Container + # - name: Snyk Container Scan + # if: steps.build.outputs.skip != 'true' + # uses: snyk/actions/docker@master + # env: + # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + # with: + # image: ${{ steps.build.outputs.image_tag }} + # args: --severity-threshold=${{ env.SEVERITY_THRESHOLD }} + + - name: Upload scan results + if: always() && steps.build.outputs.skip != 'true' + uses: actions/upload-artifact@v4 + with: + name: container-scan-${{ matrix.image.name }} + path: | + scan-results/ + *.sarif + *.json + retention-days: 30 + if-no-files-found: ignore + + - name: Cleanup + if: always() + run: | + docker rmi "${{ steps.build.outputs.image_tag }}" 2>/dev/null || true + + summary: + name: Scan Summary + runs-on: ubuntu-latest + needs: [discover-images, scan-images] + if: always() + + steps: + - name: Download all scan results + uses: actions/download-artifact@v4 + with: + pattern: container-scan-* + path: all-results/ + merge-multiple: true + continue-on-error: true + + - name: Generate summary + run: | + echo "## Container Security Scan Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Image | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + + IMAGES='${{ needs.discover-images.outputs.images }}' + SCAN_RESULT="${{ needs.scan-images.result }}" + + echo "$IMAGES" | jq -r '.[] | .name' | while read -r name; do + if [[ "$SCAN_RESULT" == "success" ]]; then + echo "| $name | No vulnerabilities found |" >> $GITHUB_STEP_SUMMARY + elif [[ "$SCAN_RESULT" == "failure" ]]; then + echo "| $name | Vulnerabilities detected |" >> $GITHUB_STEP_SUMMARY + else + echo "| $name | $SCAN_RESULT |" >> $GITHUB_STEP_SUMMARY + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Configuration" >> $GITHUB_STEP_SUMMARY + echo "- **Scanner:** Placeholder (configure in workflow)" >> $GITHUB_STEP_SUMMARY + echo "- **Severity Threshold:** ${{ env.SEVERITY_THRESHOLD }}" >> $GITHUB_STEP_SUMMARY + echo "- **Images Scanned:** ${{ needs.discover-images.outputs.count }}" >> $GITHUB_STEP_SUMMARY + echo "- **Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/dependency-license-gate.yml b/.gitea/workflows/dependency-license-gate.yml new file mode 100644 index 000000000..e492e1f49 --- /dev/null +++ b/.gitea/workflows/dependency-license-gate.yml @@ -0,0 +1,204 @@ +# Dependency License Compliance Gate +# Sprint: CI/CD Enhancement - Dependency Management Automation +# +# Purpose: Validate that all dependencies use approved licenses +# Triggers: PRs modifying package files + +name: License Compliance + +on: + pull_request: + paths: + - 'src/Directory.Packages.props' + - '**/package.json' + - '**/package-lock.json' + - '**/*.csproj' + +env: + DOTNET_VERSION: '10.0.100' + # Blocked licenses (incompatible with AGPL-3.0) + BLOCKED_LICENSES: 'GPL-2.0-only,SSPL-1.0,BUSL-1.1,Proprietary,Commercial' + # Allowed licenses + ALLOWED_LICENSES: 'MIT,Apache-2.0,BSD-2-Clause,BSD-3-Clause,ISC,0BSD,Unlicense,CC0-1.0,LGPL-2.1,LGPL-3.0,MPL-2.0,AGPL-3.0,GPL-3.0' + +jobs: + check-nuget-licenses: + name: NuGet License Check + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Install dotnet-delice + run: dotnet tool install --global dotnet-delice + + - name: Restore packages + run: dotnet restore src/StellaOps.sln + + - name: Check NuGet licenses + id: nuget-check + run: | + mkdir -p license-reports + + echo "Checking NuGet package licenses..." + + # Run delice on the solution + dotnet delice src/StellaOps.sln \ + --output license-reports/nuget-licenses.json \ + --format json \ + 2>&1 | tee license-reports/nuget-check.log || true + + # Check for blocked licenses + BLOCKED_FOUND=0 + BLOCKED_PACKAGES="" + + IFS=',' read -ra BLOCKED_ARRAY <<< "$BLOCKED_LICENSES" + for license in "${BLOCKED_ARRAY[@]}"; do + if grep -qi "\"$license\"" license-reports/nuget-licenses.json 2>/dev/null; then + BLOCKED_FOUND=1 + PACKAGES=$(grep -B5 "\"$license\"" license-reports/nuget-licenses.json | grep -o '"[^"]*"' | head -1 || echo "unknown") + BLOCKED_PACKAGES="$BLOCKED_PACKAGES\n- $license: $PACKAGES" + fi + done + + if [[ $BLOCKED_FOUND -eq 1 ]]; then + echo "::error::Blocked licenses found in NuGet packages:$BLOCKED_PACKAGES" + echo "blocked=true" >> $GITHUB_OUTPUT + echo "blocked_packages<> $GITHUB_OUTPUT + echo -e "$BLOCKED_PACKAGES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "All NuGet packages have approved licenses" + echo "blocked=false" >> $GITHUB_OUTPUT + fi + + - name: Upload NuGet license report + uses: actions/upload-artifact@v4 + with: + name: nuget-license-report + path: license-reports/ + retention-days: 30 + + check-npm-licenses: + name: npm License Check + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Find package.json files + id: find-packages + run: | + PACKAGES=$(find . -name "package.json" -not -path "*/node_modules/*" -not -path "*/bin/*" -not -path "*/obj/*" | head -10) + echo "Found package.json files:" + echo "$PACKAGES" + echo "packages<> $GITHUB_OUTPUT + echo "$PACKAGES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Install license-checker + run: npm install -g license-checker + + - name: Check npm licenses + id: npm-check + run: | + mkdir -p license-reports + BLOCKED_FOUND=0 + BLOCKED_PACKAGES="" + + # Check each package.json directory + while IFS= read -r pkg; do + if [[ -z "$pkg" ]]; then continue; fi + + DIR=$(dirname "$pkg") + echo "Checking $DIR..." + + cd "$DIR" + if [[ -f "package-lock.json" ]] || [[ -f "yarn.lock" ]]; then + npm install --ignore-scripts 2>/dev/null || true + + # Run license checker + license-checker --json > "${GITHUB_WORKSPACE}/license-reports/npm-$(basename $DIR).json" 2>/dev/null || true + + # Check for blocked licenses + IFS=',' read -ra BLOCKED_ARRAY <<< "$BLOCKED_LICENSES" + for license in "${BLOCKED_ARRAY[@]}"; do + if grep -qi "\"$license\"" "${GITHUB_WORKSPACE}/license-reports/npm-$(basename $DIR).json" 2>/dev/null; then + BLOCKED_FOUND=1 + BLOCKED_PACKAGES="$BLOCKED_PACKAGES\n- $license in $DIR" + fi + done + fi + cd "$GITHUB_WORKSPACE" + done <<< "${{ steps.find-packages.outputs.packages }}" + + if [[ $BLOCKED_FOUND -eq 1 ]]; then + echo "::error::Blocked licenses found in npm packages:$BLOCKED_PACKAGES" + echo "blocked=true" >> $GITHUB_OUTPUT + else + echo "All npm packages have approved licenses" + echo "blocked=false" >> $GITHUB_OUTPUT + fi + + - name: Upload npm license report + uses: actions/upload-artifact@v4 + if: always() + with: + name: npm-license-report + path: license-reports/ + retention-days: 30 + + gate: + name: License Gate + runs-on: ubuntu-latest + needs: [check-nuget-licenses, check-npm-licenses] + if: always() + steps: + - name: Check results + run: | + NUGET_BLOCKED="${{ needs.check-nuget-licenses.outputs.blocked }}" + NPM_BLOCKED="${{ needs.check-npm-licenses.outputs.blocked }}" + + echo "## License Compliance Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + + if [[ "$NUGET_BLOCKED" == "true" ]]; then + echo "| NuGet | ❌ Blocked licenses found |" >> $GITHUB_STEP_SUMMARY + else + echo "| NuGet | ✅ Approved |" >> $GITHUB_STEP_SUMMARY + fi + + if [[ "$NPM_BLOCKED" == "true" ]]; then + echo "| npm | ❌ Blocked licenses found |" >> $GITHUB_STEP_SUMMARY + else + echo "| npm | ✅ Approved |" >> $GITHUB_STEP_SUMMARY + fi + + if [[ "$NUGET_BLOCKED" == "true" ]] || [[ "$NPM_BLOCKED" == "true" ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Blocked Licenses" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The following licenses are not compatible with AGPL-3.0:" >> $GITHUB_STEP_SUMMARY + echo "\`$BLOCKED_LICENSES\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please replace the offending packages or request an exception." >> $GITHUB_STEP_SUMMARY + + echo "::error::License compliance check failed" + exit 1 + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ All dependencies use approved licenses" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/dependency-security-scan.yml b/.gitea/workflows/dependency-security-scan.yml new file mode 100644 index 000000000..09d662aa8 --- /dev/null +++ b/.gitea/workflows/dependency-security-scan.yml @@ -0,0 +1,249 @@ +# Dependency Security Scan +# Sprint: CI/CD Enhancement - Dependency Management Automation +# +# Purpose: Scan dependencies for known vulnerabilities +# Schedule: Weekly and on PRs modifying package files + +name: Dependency Security Scan + +on: + schedule: + # Run weekly on Sundays at 02:00 UTC + - cron: '0 2 * * 0' + pull_request: + paths: + - 'src/Directory.Packages.props' + - '**/package.json' + - '**/package-lock.json' + - '**/*.csproj' + workflow_dispatch: + inputs: + fail_on_vulnerabilities: + description: 'Fail if vulnerabilities found' + required: false + type: boolean + default: true + +env: + DOTNET_VERSION: '10.0.100' + +jobs: + scan-nuget: + name: NuGet Vulnerability Scan + runs-on: ubuntu-latest + outputs: + vulnerabilities_found: ${{ steps.scan.outputs.vulnerabilities_found }} + critical_count: ${{ steps.scan.outputs.critical_count }} + high_count: ${{ steps.scan.outputs.high_count }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Restore packages + run: dotnet restore src/StellaOps.sln + + - name: Scan for vulnerabilities + id: scan + run: | + mkdir -p security-reports + + echo "Scanning NuGet packages for vulnerabilities..." + + # Run vulnerability check + dotnet list src/StellaOps.sln package --vulnerable --include-transitive \ + > security-reports/nuget-vulnerabilities.txt 2>&1 || true + + # Parse results + CRITICAL=$(grep -c "Critical" security-reports/nuget-vulnerabilities.txt 2>/dev/null || echo "0") + HIGH=$(grep -c "High" security-reports/nuget-vulnerabilities.txt 2>/dev/null || echo "0") + MEDIUM=$(grep -c "Medium" security-reports/nuget-vulnerabilities.txt 2>/dev/null || echo "0") + LOW=$(grep -c "Low" security-reports/nuget-vulnerabilities.txt 2>/dev/null || echo "0") + + TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW)) + + echo "=== Vulnerability Summary ===" + echo "Critical: $CRITICAL" + echo "High: $HIGH" + echo "Medium: $MEDIUM" + echo "Low: $LOW" + echo "Total: $TOTAL" + + echo "critical_count=$CRITICAL" >> $GITHUB_OUTPUT + echo "high_count=$HIGH" >> $GITHUB_OUTPUT + echo "medium_count=$MEDIUM" >> $GITHUB_OUTPUT + echo "low_count=$LOW" >> $GITHUB_OUTPUT + + if [[ $TOTAL -gt 0 ]]; then + echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT + else + echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT + fi + + # Show detailed report + echo "" + echo "=== Detailed Report ===" + cat security-reports/nuget-vulnerabilities.txt + + - name: Upload NuGet security report + uses: actions/upload-artifact@v4 + with: + name: nuget-security-report + path: security-reports/ + retention-days: 90 + + scan-npm: + name: npm Vulnerability Scan + runs-on: ubuntu-latest + outputs: + vulnerabilities_found: ${{ steps.scan.outputs.vulnerabilities_found }} + critical_count: ${{ steps.scan.outputs.critical_count }} + high_count: ${{ steps.scan.outputs.high_count }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Find and scan package.json files + id: scan + run: | + mkdir -p security-reports + + TOTAL_CRITICAL=0 + TOTAL_HIGH=0 + TOTAL_MEDIUM=0 + TOTAL_LOW=0 + VULNERABILITIES_FOUND=false + + # Find all package.json files + PACKAGES=$(find . -name "package.json" -not -path "*/node_modules/*" -not -path "*/bin/*" -not -path "*/obj/*") + + for pkg in $PACKAGES; do + DIR=$(dirname "$pkg") + if [[ ! -f "$DIR/package-lock.json" ]] && [[ ! -f "$DIR/yarn.lock" ]]; then + continue + fi + + echo "Scanning $DIR..." + cd "$DIR" + + # Install dependencies + npm install --ignore-scripts 2>/dev/null || true + + # Run npm audit + REPORT_FILE="${GITHUB_WORKSPACE}/security-reports/npm-audit-$(basename $DIR).json" + npm audit --json > "$REPORT_FILE" 2>/dev/null || true + + # Parse results + if [[ -f "$REPORT_FILE" ]]; then + CRITICAL=$(jq '.metadata.vulnerabilities.critical // 0' "$REPORT_FILE" 2>/dev/null || echo "0") + HIGH=$(jq '.metadata.vulnerabilities.high // 0' "$REPORT_FILE" 2>/dev/null || echo "0") + MEDIUM=$(jq '.metadata.vulnerabilities.moderate // 0' "$REPORT_FILE" 2>/dev/null || echo "0") + LOW=$(jq '.metadata.vulnerabilities.low // 0' "$REPORT_FILE" 2>/dev/null || echo "0") + + TOTAL_CRITICAL=$((TOTAL_CRITICAL + CRITICAL)) + TOTAL_HIGH=$((TOTAL_HIGH + HIGH)) + TOTAL_MEDIUM=$((TOTAL_MEDIUM + MEDIUM)) + TOTAL_LOW=$((TOTAL_LOW + LOW)) + + if [[ $((CRITICAL + HIGH + MEDIUM + LOW)) -gt 0 ]]; then + VULNERABILITIES_FOUND=true + fi + fi + + cd "$GITHUB_WORKSPACE" + done + + echo "=== npm Vulnerability Summary ===" + echo "Critical: $TOTAL_CRITICAL" + echo "High: $TOTAL_HIGH" + echo "Medium: $TOTAL_MEDIUM" + echo "Low: $TOTAL_LOW" + + echo "critical_count=$TOTAL_CRITICAL" >> $GITHUB_OUTPUT + echo "high_count=$TOTAL_HIGH" >> $GITHUB_OUTPUT + echo "vulnerabilities_found=$VULNERABILITIES_FOUND" >> $GITHUB_OUTPUT + + - name: Upload npm security report + uses: actions/upload-artifact@v4 + with: + name: npm-security-report + path: security-reports/ + retention-days: 90 + + summary: + name: Security Summary + runs-on: ubuntu-latest + needs: [scan-nuget, scan-npm] + if: always() + + steps: + - name: Generate summary + run: | + NUGET_VULNS="${{ needs.scan-nuget.outputs.vulnerabilities_found }}" + NPM_VULNS="${{ needs.scan-npm.outputs.vulnerabilities_found }}" + + NUGET_CRITICAL="${{ needs.scan-nuget.outputs.critical_count }}" + NUGET_HIGH="${{ needs.scan-nuget.outputs.high_count }}" + NPM_CRITICAL="${{ needs.scan-npm.outputs.critical_count }}" + NPM_HIGH="${{ needs.scan-npm.outputs.high_count }}" + + echo "## Dependency Security Scan Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### NuGet Packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Critical | ${NUGET_CRITICAL:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| High | ${NUGET_HIGH:-0} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "### npm Packages" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Critical | ${NPM_CRITICAL:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| High | ${NPM_HIGH:-0} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Determine overall status + TOTAL_CRITICAL=$((${NUGET_CRITICAL:-0} + ${NPM_CRITICAL:-0})) + TOTAL_HIGH=$((${NUGET_HIGH:-0} + ${NPM_HIGH:-0})) + + if [[ $TOTAL_CRITICAL -gt 0 ]]; then + echo "### ⚠️ Critical Vulnerabilities Found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please review and remediate critical vulnerabilities before merging." >> $GITHUB_STEP_SUMMARY + elif [[ $TOTAL_HIGH -gt 0 ]]; then + echo "### ⚠️ High Severity Vulnerabilities Found" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please review high severity vulnerabilities." >> $GITHUB_STEP_SUMMARY + else + echo "### ✅ No Critical or High Vulnerabilities" >> $GITHUB_STEP_SUMMARY + fi + + - name: Check gate + if: github.event.inputs.fail_on_vulnerabilities == 'true' || github.event_name == 'pull_request' + run: | + NUGET_CRITICAL="${{ needs.scan-nuget.outputs.critical_count }}" + NPM_CRITICAL="${{ needs.scan-npm.outputs.critical_count }}" + + TOTAL_CRITICAL=$((${NUGET_CRITICAL:-0} + ${NPM_CRITICAL:-0})) + + if [[ $TOTAL_CRITICAL -gt 0 ]]; then + echo "::error::$TOTAL_CRITICAL critical vulnerabilities found in dependencies" + exit 1 + fi + + echo "Security scan passed - no critical vulnerabilities" diff --git a/.gitea/workflows/migration-test.yml b/.gitea/workflows/migration-test.yml new file mode 100644 index 000000000..e9a68070a --- /dev/null +++ b/.gitea/workflows/migration-test.yml @@ -0,0 +1,512 @@ +# .gitea/workflows/migration-test.yml +# Database Migration Testing Workflow +# Sprint: CI/CD Enhancement - Migration Safety +# +# Purpose: Validate database migrations work correctly in both directions +# - Forward migrations (upgrade) +# - Backward migrations (rollback) +# - Idempotency checks (re-running migrations) +# - Data integrity verification +# +# Triggers: +# - Pull requests that modify migration files +# - Scheduled daily validation +# - Manual dispatch for full migration suite +# +# Prerequisites: +# - PostgreSQL 16+ database +# - EF Core migrations in src/**/Migrations/ +# - Migration scripts in devops/database/migrations/ + +name: Migration Testing + +on: + push: + branches: [main] + paths: + - '**/Migrations/**' + - 'devops/database/**' + pull_request: + paths: + - '**/Migrations/**' + - 'devops/database/**' + schedule: + - cron: '30 4 * * *' # Daily at 4:30 AM UTC + workflow_dispatch: + inputs: + test_rollback: + description: 'Test rollback migrations' + type: boolean + default: true + test_idempotency: + description: 'Test migration idempotency' + type: boolean + default: true + target_module: + description: 'Specific module to test (empty = all)' + type: string + default: '' + baseline_version: + description: 'Baseline version to test from' + type: string + default: '' + +env: + DOTNET_VERSION: '10.0.100' + DOTNET_NOLOGO: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + TZ: UTC + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + POSTGRES_USER: stellaops_migration + POSTGRES_PASSWORD: migration_test_password + POSTGRES_DB: stellaops_migration_test + +jobs: + # =========================================================================== + # DISCOVER MODULES WITH MIGRATIONS + # =========================================================================== + + discover: + name: Discover Migrations + runs-on: ubuntu-22.04 + outputs: + modules: ${{ steps.find.outputs.modules }} + module_count: ${{ steps.find.outputs.count }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Find modules with migrations + id: find + run: | + # Find all EF Core migration directories + MODULES=$(find src -type d -name "Migrations" -path "*/Persistence/*" | \ + sed 's|/Migrations||' | \ + sort -u | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + COUNT=$(echo "$MODULES" | jq 'length') + + echo "Found $COUNT modules with migrations" + echo "$MODULES" | jq -r '.[]' + + # Filter by target module if specified + if [[ -n "${{ github.event.inputs.target_module }}" ]]; then + MODULES=$(echo "$MODULES" | jq -c --arg target "${{ github.event.inputs.target_module }}" \ + 'map(select(contains($target)))') + COUNT=$(echo "$MODULES" | jq 'length') + echo "Filtered to $COUNT modules matching: ${{ github.event.inputs.target_module }}" + fi + + echo "modules=$MODULES" >> $GITHUB_OUTPUT + echo "count=$COUNT" >> $GITHUB_OUTPUT + + - name: Display discovered modules + run: | + echo "## Discovered Migration Modules" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Module | Path |" >> $GITHUB_STEP_SUMMARY + echo "|--------|------|" >> $GITHUB_STEP_SUMMARY + for path in $(echo '${{ steps.find.outputs.modules }}' | jq -r '.[]'); do + module=$(basename $(dirname "$path")) + echo "| $module | $path |" >> $GITHUB_STEP_SUMMARY + done + + # =========================================================================== + # FORWARD MIGRATION TESTS + # =========================================================================== + + forward-migrations: + name: Forward Migration + runs-on: ubuntu-22.04 + timeout-minutes: 30 + needs: discover + if: needs.discover.outputs.module_count != '0' + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: ${{ env.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + POSTGRES_DB: ${{ env.POSTGRES_DB }} + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Install EF Core tools + run: dotnet tool install -g dotnet-ef + + - name: Get module name + id: module + run: | + MODULE_NAME=$(basename $(dirname "${{ matrix.module }}")) + echo "name=$MODULE_NAME" >> $GITHUB_OUTPUT + echo "Testing module: $MODULE_NAME" + + - name: Find project file + id: project + run: | + # Find the csproj file in the persistence directory + PROJECT_FILE=$(find "${{ matrix.module }}" -maxdepth 1 -name "*.csproj" | head -1) + if [[ -z "$PROJECT_FILE" ]]; then + echo "::error::No project file found in ${{ matrix.module }}" + exit 1 + fi + echo "project=$PROJECT_FILE" >> $GITHUB_OUTPUT + echo "Found project: $PROJECT_FILE" + + - name: Create fresh database + run: | + PGPASSWORD=${{ env.POSTGRES_PASSWORD }} psql -h ${{ env.POSTGRES_HOST }} \ + -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "DROP DATABASE IF EXISTS ${{ env.POSTGRES_DB }}_${{ steps.module.outputs.name }};" + PGPASSWORD=${{ env.POSTGRES_PASSWORD }} psql -h ${{ env.POSTGRES_HOST }} \ + -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "CREATE DATABASE ${{ env.POSTGRES_DB }}_${{ steps.module.outputs.name }};" + + - name: Apply all migrations (forward) + id: forward + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + echo "Applying migrations for ${{ steps.module.outputs.name }}..." + + # List available migrations first + dotnet ef migrations list --project "${{ steps.project.outputs.project }}" \ + --no-build 2>/dev/null || true + + # Apply all migrations + START_TIME=$(date +%s) + dotnet ef database update --project "${{ steps.project.outputs.project }}" + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + + echo "duration=$DURATION" >> $GITHUB_OUTPUT + echo "Migration completed in ${DURATION}s" + + - name: Verify schema + env: + PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} + run: | + echo "## Schema verification for ${{ steps.module.outputs.name }}" >> $GITHUB_STEP_SUMMARY + + # Get table count + TABLE_COUNT=$(psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} \ + -d "${{ env.POSTGRES_DB }}_${{ steps.module.outputs.name }}" -t -c \ + "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';") + + echo "- Tables created: $TABLE_COUNT" >> $GITHUB_STEP_SUMMARY + echo "- Migration time: ${{ steps.forward.outputs.duration }}s" >> $GITHUB_STEP_SUMMARY + + # List tables + echo "" >> $GITHUB_STEP_SUMMARY + echo "
Tables" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} \ + -d "${{ env.POSTGRES_DB }}_${{ steps.module.outputs.name }}" -c \ + "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name;" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + - name: Upload migration log + uses: actions/upload-artifact@v4 + if: always() + with: + name: migration-forward-${{ steps.module.outputs.name }} + path: | + **/*.migration.log + retention-days: 7 + + # =========================================================================== + # ROLLBACK MIGRATION TESTS + # =========================================================================== + + rollback-migrations: + name: Rollback Migration + runs-on: ubuntu-22.04 + timeout-minutes: 30 + needs: [discover, forward-migrations] + if: | + needs.discover.outputs.module_count != '0' && + (github.event_name == 'schedule' || github.event.inputs.test_rollback == 'true') + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: ${{ env.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + POSTGRES_DB: ${{ env.POSTGRES_DB }} + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Install EF Core tools + run: dotnet tool install -g dotnet-ef + + - name: Get module info + id: module + run: | + MODULE_NAME=$(basename $(dirname "${{ matrix.module }}")) + echo "name=$MODULE_NAME" >> $GITHUB_OUTPUT + + PROJECT_FILE=$(find "${{ matrix.module }}" -maxdepth 1 -name "*.csproj" | head -1) + echo "project=$PROJECT_FILE" >> $GITHUB_OUTPUT + + - name: Create and migrate database + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} + run: | + # Create database + psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "DROP DATABASE IF EXISTS ${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};" + psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "CREATE DATABASE ${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};" + + # Apply all migrations + dotnet ef database update --project "${{ steps.module.outputs.project }}" + + - name: Get migration list + id: migrations + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + # Get list of applied migrations + MIGRATIONS=$(dotnet ef migrations list --project "${{ steps.module.outputs.project }}" \ + --no-build 2>/dev/null | grep -E "^\d{14}_" | tail -5) + + MIGRATION_COUNT=$(echo "$MIGRATIONS" | wc -l) + echo "count=$MIGRATION_COUNT" >> $GITHUB_OUTPUT + + if [[ $MIGRATION_COUNT -gt 1 ]]; then + # Get the second-to-last migration for rollback target + ROLLBACK_TARGET=$(echo "$MIGRATIONS" | tail -2 | head -1) + echo "rollback_to=$ROLLBACK_TARGET" >> $GITHUB_OUTPUT + echo "Will rollback to: $ROLLBACK_TARGET" + else + echo "rollback_to=" >> $GITHUB_OUTPUT + echo "Not enough migrations to test rollback" + fi + + - name: Test rollback + if: steps.migrations.outputs.rollback_to != '' + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + echo "Rolling back to: ${{ steps.migrations.outputs.rollback_to }}" + dotnet ef database update "${{ steps.migrations.outputs.rollback_to }}" \ + --project "${{ steps.module.outputs.project }}" + + echo "Rollback successful!" + + - name: Test re-apply after rollback + if: steps.migrations.outputs.rollback_to != '' + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_rb_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + echo "Re-applying migrations after rollback..." + dotnet ef database update --project "${{ steps.module.outputs.project }}" + + echo "Re-apply successful!" + + - name: Report rollback results + if: always() + run: | + echo "## Rollback Test: ${{ steps.module.outputs.name }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [[ -n "${{ steps.migrations.outputs.rollback_to }}" ]]; then + echo "- Rollback target: ${{ steps.migrations.outputs.rollback_to }}" >> $GITHUB_STEP_SUMMARY + echo "- Status: Tested" >> $GITHUB_STEP_SUMMARY + else + echo "- Status: Skipped (insufficient migrations)" >> $GITHUB_STEP_SUMMARY + fi + + # =========================================================================== + # IDEMPOTENCY TESTS + # =========================================================================== + + idempotency: + name: Idempotency Test + runs-on: ubuntu-22.04 + timeout-minutes: 20 + needs: [discover, forward-migrations] + if: | + needs.discover.outputs.module_count != '0' && + (github.event_name == 'schedule' || github.event.inputs.test_idempotency == 'true') + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: ${{ env.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }} + POSTGRES_DB: ${{ env.POSTGRES_DB }} + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + fail-fast: false + matrix: + module: ${{ fromJson(needs.discover.outputs.modules) }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Install EF Core tools + run: dotnet tool install -g dotnet-ef + + - name: Get module info + id: module + run: | + MODULE_NAME=$(basename $(dirname "${{ matrix.module }}")) + echo "name=$MODULE_NAME" >> $GITHUB_OUTPUT + + PROJECT_FILE=$(find "${{ matrix.module }}" -maxdepth 1 -name "*.csproj" | head -1) + echo "project=$PROJECT_FILE" >> $GITHUB_OUTPUT + + - name: Setup database + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} + run: | + psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "DROP DATABASE IF EXISTS ${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }};" + psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} -d postgres \ + -c "CREATE DATABASE ${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }};" + + - name: First migration run + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + dotnet ef database update --project "${{ steps.module.outputs.project }}" + + - name: Get initial schema hash + id: hash1 + env: + PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} + run: | + SCHEMA_HASH=$(psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} \ + -d "${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }}" -t -c \ + "SELECT md5(string_agg(table_name || column_name || data_type, '' ORDER BY table_name, column_name)) + FROM information_schema.columns WHERE table_schema = 'public';") + echo "hash=$SCHEMA_HASH" >> $GITHUB_OUTPUT + echo "Initial schema hash: $SCHEMA_HASH" + + - name: Second migration run (idempotency test) + env: + ConnectionStrings__Default: "Host=${{ env.POSTGRES_HOST }};Port=${{ env.POSTGRES_PORT }};Database=${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }};Username=${{ env.POSTGRES_USER }};Password=${{ env.POSTGRES_PASSWORD }}" + run: | + # Running migrations again should be a no-op + dotnet ef database update --project "${{ steps.module.outputs.project }}" + + - name: Get final schema hash + id: hash2 + env: + PGPASSWORD: ${{ env.POSTGRES_PASSWORD }} + run: | + SCHEMA_HASH=$(psql -h ${{ env.POSTGRES_HOST }} -U ${{ env.POSTGRES_USER }} \ + -d "${{ env.POSTGRES_DB }}_idem_${{ steps.module.outputs.name }}" -t -c \ + "SELECT md5(string_agg(table_name || column_name || data_type, '' ORDER BY table_name, column_name)) + FROM information_schema.columns WHERE table_schema = 'public';") + echo "hash=$SCHEMA_HASH" >> $GITHUB_OUTPUT + echo "Final schema hash: $SCHEMA_HASH" + + - name: Verify idempotency + run: | + HASH1="${{ steps.hash1.outputs.hash }}" + HASH2="${{ steps.hash2.outputs.hash }}" + + echo "## Idempotency Test: ${{ steps.module.outputs.name }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- Initial schema hash: $HASH1" >> $GITHUB_STEP_SUMMARY + echo "- Final schema hash: $HASH2" >> $GITHUB_STEP_SUMMARY + + if [[ "$HASH1" == "$HASH2" ]]; then + echo "- Result: PASS (schemas identical)" >> $GITHUB_STEP_SUMMARY + else + echo "- Result: FAIL (schemas differ)" >> $GITHUB_STEP_SUMMARY + echo "::error::Idempotency test failed for ${{ steps.module.outputs.name }}" + exit 1 + fi + + # =========================================================================== + # SUMMARY + # =========================================================================== + + summary: + name: Migration Summary + runs-on: ubuntu-22.04 + needs: [discover, forward-migrations, rollback-migrations, idempotency] + if: always() + steps: + - name: Generate Summary + run: | + echo "## Migration Test Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Test | Status |" >> $GITHUB_STEP_SUMMARY + echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Discovery | ${{ needs.discover.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Forward Migrations | ${{ needs.forward-migrations.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Rollback Migrations | ${{ needs.rollback-migrations.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Idempotency | ${{ needs.idempotency.result }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Modules Tested: ${{ needs.discover.outputs.module_count }}" >> $GITHUB_STEP_SUMMARY + + - name: Check for failures + if: contains(needs.*.result, 'failure') + run: exit 1 diff --git a/.gitea/workflows/nightly-regression.yml b/.gitea/workflows/nightly-regression.yml new file mode 100644 index 000000000..767df859d --- /dev/null +++ b/.gitea/workflows/nightly-regression.yml @@ -0,0 +1,483 @@ +# .gitea/workflows/nightly-regression.yml +# Nightly Full-Suite Regression Testing +# Sprint: CI/CD Enhancement - Comprehensive Testing +# +# Purpose: Run comprehensive regression tests that are too expensive for PR gating +# - Full test matrix (all categories) +# - Extended integration tests +# - Performance benchmarks with historical comparison +# - Cross-module dependency validation +# - Determinism verification +# +# Schedule: Daily at 2:00 AM UTC (off-peak hours) +# +# Notifications: Slack/Teams on failure + +name: Nightly Regression + +on: + schedule: + - cron: '0 2 * * *' # Daily at 2:00 AM UTC + workflow_dispatch: + inputs: + skip_performance: + description: 'Skip performance tests' + type: boolean + default: false + skip_determinism: + description: 'Skip determinism tests' + type: boolean + default: false + notify_on_success: + description: 'Send notification on success' + type: boolean + default: false + +env: + DOTNET_VERSION: '10.0.100' + DOTNET_NOLOGO: 1 + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1 + TZ: UTC + +jobs: + # =========================================================================== + # PREPARE NIGHTLY RUN + # =========================================================================== + + prepare: + name: Prepare Nightly Run + runs-on: ubuntu-22.04 + outputs: + run_id: ${{ steps.metadata.outputs.run_id }} + run_date: ${{ steps.metadata.outputs.run_date }} + commit_sha: ${{ steps.metadata.outputs.commit_sha }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate run metadata + id: metadata + run: | + RUN_ID="nightly-$(date -u +%Y%m%d-%H%M%S)" + RUN_DATE=$(date -u +%Y-%m-%d) + COMMIT_SHA=$(git rev-parse HEAD) + + echo "run_id=$RUN_ID" >> $GITHUB_OUTPUT + echo "run_date=$RUN_DATE" >> $GITHUB_OUTPUT + echo "commit_sha=$COMMIT_SHA" >> $GITHUB_OUTPUT + + echo "## Nightly Regression Run" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Run ID:** $RUN_ID" >> $GITHUB_STEP_SUMMARY + echo "- **Date:** $RUN_DATE" >> $GITHUB_STEP_SUMMARY + echo "- **Commit:** $COMMIT_SHA" >> $GITHUB_STEP_SUMMARY + + - name: Check recent commits + run: | + echo "### Recent Commits" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + git log --oneline -10 >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + # =========================================================================== + # FULL BUILD VERIFICATION + # =========================================================================== + + build: + name: Full Build + runs-on: ubuntu-22.04 + timeout-minutes: 30 + needs: prepare + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Restore dependencies + run: dotnet restore src/StellaOps.sln + + - name: Build solution (Release) + run: | + START_TIME=$(date +%s) + dotnet build src/StellaOps.sln --configuration Release --no-restore + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "build_time=$DURATION" >> $GITHUB_ENV + echo "Build completed in ${DURATION}s" + + - name: Report build metrics + run: | + echo "### Build Metrics" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Build Time:** ${{ env.build_time }}s" >> $GITHUB_STEP_SUMMARY + echo "- **Configuration:** Release" >> $GITHUB_STEP_SUMMARY + + # =========================================================================== + # COMPREHENSIVE TEST SUITE + # =========================================================================== + + test-pr-gating: + name: PR-Gating Tests + runs-on: ubuntu-22.04 + timeout-minutes: 45 + needs: build + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: stellaops + POSTGRES_PASSWORD: stellaops + POSTGRES_DB: stellaops_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + fail-fast: false + matrix: + category: + - Unit + - Architecture + - Contract + - Integration + - Security + - Golden + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Run ${{ matrix.category }} Tests + env: + STELLAOPS_TEST_POSTGRES_CONNECTION: "Host=localhost;Port=5432;Database=stellaops_test;Username=stellaops;Password=stellaops" + run: | + chmod +x .gitea/scripts/test/run-test-category.sh + .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}" + + - name: Upload Test Results + uses: actions/upload-artifact@v4 + if: always() + with: + name: nightly-test-${{ matrix.category }} + path: ./TestResults/${{ matrix.category }} + retention-days: 30 + + test-extended: + name: Extended Tests + runs-on: ubuntu-22.04 + timeout-minutes: 60 + needs: build + if: github.event.inputs.skip_performance != 'true' + + strategy: + fail-fast: false + matrix: + category: + - Performance + - Benchmark + - Resilience + - Observability + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Run ${{ matrix.category }} Tests + run: | + chmod +x .gitea/scripts/test/run-test-category.sh + .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}" + + - name: Upload Test Results + uses: actions/upload-artifact@v4 + if: always() + with: + name: nightly-extended-${{ matrix.category }} + path: ./TestResults/${{ matrix.category }} + retention-days: 30 + + # =========================================================================== + # DETERMINISM VERIFICATION + # =========================================================================== + + determinism: + name: Determinism Verification + runs-on: ubuntu-22.04 + timeout-minutes: 45 + needs: build + if: github.event.inputs.skip_determinism != 'true' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: First build + run: | + dotnet build src/StellaOps.sln --configuration Release -o ./build-1 + find ./build-1 -name "*.dll" -exec sha256sum {} \; | sort > checksums-1.txt + + - name: Clean and rebuild + run: | + rm -rf ./build-1 + dotnet clean src/StellaOps.sln + dotnet build src/StellaOps.sln --configuration Release -o ./build-2 + find ./build-2 -name "*.dll" -exec sha256sum {} \; | sort > checksums-2.txt + + - name: Compare builds + id: compare + run: | + echo "### Determinism Check" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if diff checksums-1.txt checksums-2.txt > /dev/null; then + echo "PASS: Builds are deterministic" >> $GITHUB_STEP_SUMMARY + echo "deterministic=true" >> $GITHUB_OUTPUT + else + echo "FAIL: Builds differ" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
Differences" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```diff' >> $GITHUB_STEP_SUMMARY + diff checksums-1.txt checksums-2.txt >> $GITHUB_STEP_SUMMARY || true + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + echo "deterministic=false" >> $GITHUB_OUTPUT + exit 1 + fi + + - name: Upload checksums + uses: actions/upload-artifact@v4 + if: always() + with: + name: nightly-determinism-checksums + path: checksums-*.txt + retention-days: 30 + + # =========================================================================== + # CROSS-MODULE VALIDATION + # =========================================================================== + + cross-module: + name: Cross-Module Validation + runs-on: ubuntu-22.04 + timeout-minutes: 30 + needs: build + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Check for circular dependencies + run: | + echo "### Dependency Analysis" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Build dependency graph + echo "Analyzing project dependencies..." + for proj in $(find src -name "*.csproj" ! -path "*/bin/*" ! -path "*/obj/*" | head -50); do + # Extract ProjectReference entries + refs=$(grep -oP 'ProjectReference Include="\K[^"]+' "$proj" 2>/dev/null || true) + if [[ -n "$refs" ]]; then + basename "$proj" >> deps.txt + echo "$refs" | while read ref; do + echo " -> $(basename "$ref")" >> deps.txt + done + fi + done + + if [[ -f deps.txt ]]; then + echo "
Project Dependencies (first 50)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + head -100 deps.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + fi + + - name: Validate no deprecated APIs + run: | + # Check for use of deprecated patterns + DEPRECATED_COUNT=$(grep -r "Obsolete" src --include="*.cs" | wc -l || echo "0") + echo "- Obsolete attribute usages: $DEPRECATED_COUNT" >> $GITHUB_STEP_SUMMARY + + # =========================================================================== + # CODE COVERAGE REPORT + # =========================================================================== + + coverage: + name: Code Coverage + runs-on: ubuntu-22.04 + timeout-minutes: 45 + needs: build + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: stellaops + POSTGRES_PASSWORD: stellaops + POSTGRES_DB: stellaops_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Run tests with coverage + env: + STELLAOPS_TEST_POSTGRES_CONNECTION: "Host=localhost;Port=5432;Database=stellaops_test;Username=stellaops;Password=stellaops" + run: | + dotnet test src/StellaOps.sln \ + --configuration Release \ + --collect:"XPlat Code Coverage" \ + --results-directory ./TestResults/Coverage \ + --filter "Category=Unit|Category=Integration" \ + --verbosity minimal \ + -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura + + - name: Install ReportGenerator + run: dotnet tool install -g dotnet-reportgenerator-globaltool + + - name: Generate coverage report + run: | + reportgenerator \ + -reports:"./TestResults/Coverage/**/coverage.cobertura.xml" \ + -targetdir:"./TestResults/CoverageReport" \ + -reporttypes:"Html;MarkdownSummary;Cobertura" \ + || true + + - name: Add coverage to summary + run: | + echo "### Code Coverage Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [[ -f "./TestResults/CoverageReport/Summary.md" ]]; then + cat "./TestResults/CoverageReport/Summary.md" >> $GITHUB_STEP_SUMMARY + else + echo "Coverage report generation failed or no coverage data collected." >> $GITHUB_STEP_SUMMARY + fi + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + if: always() + with: + name: nightly-coverage-report + path: ./TestResults/CoverageReport + retention-days: 30 + + # =========================================================================== + # SUMMARY AND NOTIFICATION + # =========================================================================== + + summary: + name: Nightly Summary + runs-on: ubuntu-22.04 + needs: + - prepare + - build + - test-pr-gating + - test-extended + - determinism + - cross-module + - coverage + if: always() + steps: + - name: Generate final summary + run: | + echo "## Nightly Regression Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Run ID:** ${{ needs.prepare.outputs.run_id }}" >> $GITHUB_STEP_SUMMARY + echo "**Date:** ${{ needs.prepare.outputs.run_date }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** ${{ needs.prepare.outputs.commit_sha }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Job Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Build | ${{ needs.build.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| PR-Gating Tests | ${{ needs.test-pr-gating.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Extended Tests | ${{ needs.test-extended.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Determinism | ${{ needs.determinism.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Cross-Module | ${{ needs.cross-module.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Coverage | ${{ needs.coverage.result }} |" >> $GITHUB_STEP_SUMMARY + + - name: Determine overall status + id: status + run: | + if [[ "${{ needs.build.result }}" == "failure" ]] || \ + [[ "${{ needs.test-pr-gating.result }}" == "failure" ]] || \ + [[ "${{ needs.determinism.result }}" == "failure" ]]; then + echo "status=failure" >> $GITHUB_OUTPUT + else + echo "status=success" >> $GITHUB_OUTPUT + fi + + # Placeholder for notifications - configure webhook URL in secrets + - name: Send failure notification + if: steps.status.outputs.status == 'failure' + run: | + echo "::warning::Nightly regression failed - notification would be sent here" + # Uncomment and configure when webhook is available: + # curl -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \ + # -H "Content-Type: application/json" \ + # -d '{ + # "text": "Nightly Regression Failed", + # "attachments": [{ + # "color": "danger", + # "fields": [ + # {"title": "Run ID", "value": "${{ needs.prepare.outputs.run_id }}", "short": true}, + # {"title": "Commit", "value": "${{ needs.prepare.outputs.commit_sha }}", "short": true} + # ] + # }] + # }' + + - name: Send success notification + if: steps.status.outputs.status == 'success' && github.event.inputs.notify_on_success == 'true' + run: | + echo "::notice::Nightly regression passed" + + - name: Exit with appropriate code + if: steps.status.outputs.status == 'failure' + run: exit 1 diff --git a/.gitea/workflows/release-suite.yml b/.gitea/workflows/release-suite.yml index 5ddac4ca3..c25897b77 100644 --- a/.gitea/workflows/release-suite.yml +++ b/.gitea/workflows/release-suite.yml @@ -532,6 +532,233 @@ jobs: path: out/release retention-days: 90 + # =========================================================================== + # GENERATE CHANGELOG (AI-assisted) + # =========================================================================== + + generate-changelog: + name: Generate Changelog + runs-on: ubuntu-22.04 + needs: [validate, build-modules] + if: always() && needs.validate.result == 'success' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Find previous release tag + id: prev-tag + run: | + PREV_TAG=$(git tag -l "suite-*" --sort=-creatordate | head -1) + echo "Previous tag: ${PREV_TAG:-none}" + echo "prev_tag=${PREV_TAG}" >> $GITHUB_OUTPUT + + - name: Generate changelog + env: + AI_API_KEY: ${{ secrets.AI_API_KEY }} + run: | + VERSION="${{ needs.validate.outputs.version }}" + CODENAME="${{ needs.validate.outputs.codename }}" + PREV_TAG="${{ steps.prev-tag.outputs.prev_tag }}" + + mkdir -p out/docs + + ARGS="$VERSION --codename $CODENAME --output out/docs/CHANGELOG.md" + if [[ -n "$PREV_TAG" ]]; then + ARGS="$ARGS --from-tag $PREV_TAG" + fi + if [[ -n "$AI_API_KEY" ]]; then + ARGS="$ARGS --ai" + fi + + python3 .gitea/scripts/release/generate_changelog.py $ARGS + + echo "=== Generated Changelog ===" + head -50 out/docs/CHANGELOG.md + + - name: Upload changelog + uses: actions/upload-artifact@v4 + with: + name: changelog-${{ needs.validate.outputs.version }} + path: out/docs/CHANGELOG.md + retention-days: 90 + + # =========================================================================== + # GENERATE SUITE DOCUMENTATION + # =========================================================================== + + generate-suite-docs: + name: Generate Suite Docs + runs-on: ubuntu-22.04 + needs: [validate, generate-changelog, release-manifest] + if: always() && needs.validate.result == 'success' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: pip install python-dateutil + + - name: Download changelog + uses: actions/download-artifact@v4 + with: + name: changelog-${{ needs.validate.outputs.version }} + path: changelog + + - name: Find previous version + id: prev-version + run: | + PREV_TAG=$(git tag -l "suite-*" --sort=-creatordate | head -1) + if [[ -n "$PREV_TAG" ]]; then + PREV_VERSION=$(echo "$PREV_TAG" | sed 's/suite-//') + echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT + fi + + - name: Generate suite documentation + run: | + VERSION="${{ needs.validate.outputs.version }}" + CODENAME="${{ needs.validate.outputs.codename }}" + CHANNEL="${{ needs.validate.outputs.channel }}" + PREV="${{ steps.prev-version.outputs.prev_version }}" + + ARGS="$VERSION $CODENAME --channel $CHANNEL" + if [[ -f "changelog/CHANGELOG.md" ]]; then + ARGS="$ARGS --changelog changelog/CHANGELOG.md" + fi + if [[ -n "$PREV" ]]; then + ARGS="$ARGS --previous $PREV" + fi + + python3 .gitea/scripts/release/generate_suite_docs.py $ARGS + + echo "=== Generated Documentation ===" + ls -la docs/releases/$VERSION/ + + - name: Upload suite docs + uses: actions/upload-artifact@v4 + with: + name: suite-docs-${{ needs.validate.outputs.version }} + path: docs/releases/${{ needs.validate.outputs.version }} + retention-days: 90 + + # =========================================================================== + # GENERATE DOCKER COMPOSE FILES + # =========================================================================== + + generate-compose: + name: Generate Docker Compose + runs-on: ubuntu-22.04 + needs: [validate, release-manifest] + if: always() && needs.validate.result == 'success' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Generate Docker Compose files + run: | + VERSION="${{ needs.validate.outputs.version }}" + CODENAME="${{ needs.validate.outputs.codename }}" + + mkdir -p out/compose + + # Standard compose + python3 .gitea/scripts/release/generate_compose.py \ + "$VERSION" "$CODENAME" \ + --output out/compose/docker-compose.yml + + # Air-gap variant + python3 .gitea/scripts/release/generate_compose.py \ + "$VERSION" "$CODENAME" \ + --airgap \ + --output out/compose/docker-compose.airgap.yml + + echo "=== Generated Compose Files ===" + ls -la out/compose/ + + - name: Upload compose files + uses: actions/upload-artifact@v4 + with: + name: compose-${{ needs.validate.outputs.version }} + path: out/compose + retention-days: 90 + + # =========================================================================== + # COMMIT DOCS TO REPOSITORY + # =========================================================================== + + commit-docs: + name: Commit Documentation + runs-on: ubuntu-22.04 + needs: [validate, generate-suite-docs, generate-compose, create-release] + if: needs.validate.outputs.dry_run != 'true' && needs.create-release.result == 'success' + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITEA_TOKEN }} + fetch-depth: 0 + + - name: Download suite docs + uses: actions/download-artifact@v4 + with: + name: suite-docs-${{ needs.validate.outputs.version }} + path: docs/releases/${{ needs.validate.outputs.version }} + + - name: Download compose files + uses: actions/download-artifact@v4 + with: + name: compose-${{ needs.validate.outputs.version }} + path: docs/releases/${{ needs.validate.outputs.version }} + + - name: Commit documentation + run: | + VERSION="${{ needs.validate.outputs.version }}" + CODENAME="${{ needs.validate.outputs.codename }}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add "docs/releases/${VERSION}" + + if git diff --cached --quiet; then + echo "No documentation changes to commit" + else + git commit -m "docs: add release documentation for ${VERSION} ${CODENAME} + + Generated documentation for StellaOps ${VERSION} \"${CODENAME}\" + + - README.md + - CHANGELOG.md + - services.md + - upgrade-guide.md + - docker-compose.yml + - docker-compose.airgap.yml + - manifest.yaml + + 🤖 Generated with [Claude Code](https://claude.com/claude-code) + + Co-Authored-By: github-actions[bot] " + + git push + echo "Documentation committed and pushed" + fi + # =========================================================================== # CREATE GITEA RELEASE # =========================================================================== @@ -651,7 +878,7 @@ jobs: summary: name: Release Summary runs-on: ubuntu-22.04 - needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest, create-release] + needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest, generate-changelog, generate-suite-docs, generate-compose, create-release, commit-docs] if: always() steps: - name: Generate Summary @@ -674,7 +901,11 @@ jobs: echo "| Build CLI | ${{ needs.build-cli.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Build Helm | ${{ needs.build-helm.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Release Manifest | ${{ needs.release-manifest.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Generate Changelog | ${{ needs.generate-changelog.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Generate Suite Docs | ${{ needs.generate-suite-docs.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Generate Compose | ${{ needs.generate-compose.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Create Release | ${{ needs.create-release.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Commit Documentation | ${{ needs.commit-docs.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY - name: Check for failures if: contains(needs.*.result, 'failure') diff --git a/.gitea/workflows/renovate.yml b/.gitea/workflows/renovate.yml new file mode 100644 index 000000000..9656652ed --- /dev/null +++ b/.gitea/workflows/renovate.yml @@ -0,0 +1,114 @@ +# Renovate Bot Workflow for Gitea +# Sprint: CI/CD Enhancement - Dependency Management Automation +# +# Purpose: Run Renovate Bot to automatically update dependencies +# Schedule: Twice daily (03:00 and 15:00 UTC) +# +# Requirements: +# - RENOVATE_TOKEN secret with repo write access +# - renovate.json configuration in repo root + +name: Renovate + +on: + schedule: + # Run at 03:00 and 15:00 UTC + - cron: '0 3,15 * * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (no PRs created)' + required: false + type: boolean + default: false + log_level: + description: 'Log level' + required: false + type: choice + options: + - debug + - info + - warn + default: 'info' + +env: + RENOVATE_VERSION: '37.100.0' + LOG_LEVEL: ${{ github.event.inputs.log_level || 'info' }} + +jobs: + renovate: + name: Run Renovate + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate configuration + run: | + if [[ ! -f "renovate.json" ]]; then + echo "::error::renovate.json not found in repository root" + exit 1 + fi + echo "Renovate configuration found" + cat renovate.json | head -20 + + - name: Run Renovate + env: + RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} + RENOVATE_PLATFORM: gitea + RENOVATE_ENDPOINT: ${{ github.server_url }}/api/v1 + RENOVATE_REPOSITORIES: ${{ github.repository }} + RENOVATE_DRY_RUN: ${{ github.event.inputs.dry_run == 'true' && 'full' || 'null' }} + LOG_LEVEL: ${{ env.LOG_LEVEL }} + run: | + # Install Renovate + npm install -g renovate@${{ env.RENOVATE_VERSION }} + + # Configure Renovate + export RENOVATE_CONFIG_FILE="${GITHUB_WORKSPACE}/renovate.json" + + # Set dry run mode + if [[ "$RENOVATE_DRY_RUN" == "full" ]]; then + echo "Running in DRY RUN mode - no PRs will be created" + export RENOVATE_DRY_RUN="full" + fi + + # Run Renovate + renovate \ + --platform="$RENOVATE_PLATFORM" \ + --endpoint="$RENOVATE_ENDPOINT" \ + --token="$RENOVATE_TOKEN" \ + "$RENOVATE_REPOSITORIES" \ + 2>&1 | tee renovate.log + + - name: Upload Renovate log + uses: actions/upload-artifact@v4 + if: always() + with: + name: renovate-log-${{ github.run_id }} + path: renovate.log + retention-days: 7 + + - name: Summary + if: always() + run: | + echo "## Renovate Run Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Version | ${{ env.RENOVATE_VERSION }} |" >> $GITHUB_STEP_SUMMARY + echo "| Log Level | ${{ env.LOG_LEVEL }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dry Run | ${{ github.event.inputs.dry_run || 'false' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Trigger | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ -f renovate.log ]]; then + # Count PRs created/updated + CREATED=$(grep -c "PR created" renovate.log 2>/dev/null || echo "0") + UPDATED=$(grep -c "PR updated" renovate.log 2>/dev/null || echo "0") + echo "### Results" >> $GITHUB_STEP_SUMMARY + echo "- PRs Created: $CREATED" >> $GITHUB_STEP_SUMMARY + echo "- PRs Updated: $UPDATED" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitea/workflows/rollback.yml b/.gitea/workflows/rollback.yml new file mode 100644 index 000000000..c374c6d8b --- /dev/null +++ b/.gitea/workflows/rollback.yml @@ -0,0 +1,277 @@ +# Emergency Rollback Workflow +# Sprint: CI/CD Enhancement - Deployment Safety +# +# Purpose: Automated rollback to previous known-good version +# Triggers: Manual dispatch only (emergency procedure) +# +# SLA Target: < 5 minutes from trigger to rollback complete + +name: Emergency Rollback + +on: + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + type: choice + options: + - staging + - production + service: + description: 'Service to rollback (or "all" for full rollback)' + required: true + type: choice + options: + - all + - authority + - attestor + - concelier + - scanner + - policy + - excititor + - gateway + - scheduler + - cli + target_version: + description: 'Version to rollback to (leave empty for previous version)' + required: false + type: string + reason: + description: 'Reason for rollback' + required: true + type: string + skip_health_check: + description: 'Skip health check (use only in emergencies)' + required: false + type: boolean + default: false + +env: + ROLLBACK_TIMEOUT: 300 # 5 minutes + +jobs: + validate: + name: Validate Rollback Request + runs-on: ubuntu-latest + outputs: + target_version: ${{ steps.resolve.outputs.version }} + services: ${{ steps.resolve.outputs.services }} + approved: ${{ steps.validate.outputs.approved }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate inputs + id: validate + run: | + echo "## Rollback Request Validation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Environment | ${{ inputs.environment }} |" >> $GITHUB_STEP_SUMMARY + echo "| Service | ${{ inputs.service }} |" >> $GITHUB_STEP_SUMMARY + echo "| Target Version | ${{ inputs.target_version || 'previous' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Reason | ${{ inputs.reason }} |" >> $GITHUB_STEP_SUMMARY + echo "| Triggered By | ${{ github.actor }} |" >> $GITHUB_STEP_SUMMARY + echo "| Timestamp | $(date -u +"%Y-%m-%dT%H:%M:%SZ") |" >> $GITHUB_STEP_SUMMARY + + # Production requires additional validation + if [[ "${{ inputs.environment }}" == "production" ]]; then + echo "" + echo "### Production Rollback Warning" >> $GITHUB_STEP_SUMMARY + echo "This will affect production users immediately." >> $GITHUB_STEP_SUMMARY + fi + + echo "approved=true" >> $GITHUB_OUTPUT + + - name: Resolve target version + id: resolve + run: | + VERSION="${{ inputs.target_version }}" + SERVICE="${{ inputs.service }}" + + # If no version specified, get previous from manifest + if [[ -z "$VERSION" ]]; then + MANIFEST="devops/releases/service-versions.json" + if [[ -f "$MANIFEST" ]]; then + if [[ "$SERVICE" == "all" ]]; then + # Get oldest version across all services + VERSION=$(jq -r '.services | to_entries | map(.value.version) | sort | first // "unknown"' "$MANIFEST") + else + VERSION=$(jq -r --arg svc "$SERVICE" '.services[$svc].previousVersion // .services[$svc].version // "unknown"' "$MANIFEST") + fi + fi + fi + + # Determine services to rollback + if [[ "$SERVICE" == "all" ]]; then + SERVICES='["authority","attestor","concelier","scanner","policy","excititor","gateway","scheduler"]' + else + SERVICES="[\"$SERVICE\"]" + fi + + echo "Resolved version: $VERSION" + echo "Services: $SERVICES" + + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "services=$SERVICES" >> $GITHUB_OUTPUT + + rollback: + name: Execute Rollback + runs-on: ubuntu-latest + needs: [validate] + if: needs.validate.outputs.approved == 'true' + environment: ${{ inputs.environment }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup kubectl + uses: azure/setup-kubectl@v3 + with: + version: 'latest' + + - name: Setup Helm + uses: azure/setup-helm@v3 + with: + version: 'latest' + + - name: Configure deployment access + run: | + echo "::notice::Configure deployment access for ${{ inputs.environment }}" + # TODO: Configure kubectl context / kubeconfig + # kubectl config use-context ${{ inputs.environment }} + + - name: Execute rollback + id: rollback + run: | + echo "Starting rollback..." + START_TIME=$(date +%s) + + TARGET_VERSION="${{ needs.validate.outputs.target_version }}" + SERVICES='${{ needs.validate.outputs.services }}' + ENVIRONMENT="${{ inputs.environment }}" + + # Execute rollback script + if [[ -f ".gitea/scripts/release/rollback.sh" ]]; then + .gitea/scripts/release/rollback.sh \ + --environment "$ENVIRONMENT" \ + --version "$TARGET_VERSION" \ + --services "$SERVICES" \ + --reason "${{ inputs.reason }}" + else + echo "::warning::Rollback script not found - using placeholder" + echo "" + echo "Rollback would execute:" + echo " Environment: $ENVIRONMENT" + echo " Version: $TARGET_VERSION" + echo " Services: $SERVICES" + echo "" + echo "TODO: Implement rollback.sh script" + fi + + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + + echo "duration=$DURATION" >> $GITHUB_OUTPUT + echo "Rollback completed in ${DURATION}s" + + - name: Health check + if: inputs.skip_health_check != true + run: | + echo "Running health checks..." + + SERVICES='${{ needs.validate.outputs.services }}' + + echo "$SERVICES" | jq -r '.[]' | while read -r service; do + echo "Checking $service..." + # TODO: Implement service-specific health checks + # curl -sf "https://${service}.${{ inputs.environment }}.stella-ops.org/health" || exit 1 + echo " Status: OK (placeholder)" + done + + echo "All health checks passed" + + - name: Rollback summary + if: always() + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Rollback Execution" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ steps.rollback.outcome }}" == "success" ]]; then + echo "### Rollback Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- Duration: ${{ steps.rollback.outputs.duration }}s" >> $GITHUB_STEP_SUMMARY + echo "- Target Version: ${{ needs.validate.outputs.target_version }}" >> $GITHUB_STEP_SUMMARY + else + echo "### Rollback Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please investigate immediately and consider manual intervention." >> $GITHUB_STEP_SUMMARY + fi + + notify: + name: Send Notifications + runs-on: ubuntu-latest + needs: [validate, rollback] + if: always() + + steps: + - name: Notify team + run: | + STATUS="${{ needs.rollback.result }}" + ENVIRONMENT="${{ inputs.environment }}" + SERVICE="${{ inputs.service }}" + ACTOR="${{ github.actor }}" + REASON="${{ inputs.reason }}" + VERSION="${{ needs.validate.outputs.target_version }}" + + # Build notification message + if [[ "$STATUS" == "success" ]]; then + EMOJI="white_check_mark" + TITLE="Rollback Completed Successfully" + else + EMOJI="x" + TITLE="Rollback Failed - Immediate Attention Required" + fi + + echo "Notification:" + echo " Title: $TITLE" + echo " Environment: $ENVIRONMENT" + echo " Service: $SERVICE" + echo " Version: $VERSION" + echo " Actor: $ACTOR" + echo " Reason: $REASON" + + # TODO: Send to Slack/Teams/PagerDuty + # - name: Slack notification + # uses: slackapi/slack-github-action@v1 + # with: + # payload: | + # { + # "text": "${{ env.TITLE }}", + # "blocks": [...] + # } + + - name: Create incident record + run: | + echo "Creating incident record..." + + # Log to incident tracking + INCIDENT_LOG="devops/incidents/$(date +%Y-%m-%d)-rollback.json" + echo "{ + \"timestamp\": \"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\", + \"type\": \"rollback\", + \"environment\": \"${{ inputs.environment }}\", + \"service\": \"${{ inputs.service }}\", + \"target_version\": \"${{ needs.validate.outputs.target_version }}\", + \"reason\": \"${{ inputs.reason }}\", + \"actor\": \"${{ github.actor }}\", + \"status\": \"${{ needs.rollback.result }}\", + \"run_id\": \"${{ github.run_id }}\" + }" + + echo "::notice::Incident record would be created at $INCIDENT_LOG" diff --git a/.gitea/workflows/sast-scan.yml b/.gitea/workflows/sast-scan.yml new file mode 100644 index 000000000..7f44b8cd4 --- /dev/null +++ b/.gitea/workflows/sast-scan.yml @@ -0,0 +1,386 @@ +# .gitea/workflows/sast-scan.yml +# Static Application Security Testing (SAST) Workflow +# Sprint: CI/CD Enhancement - Security Scanning (Tier 2) +# +# Purpose: Detect security vulnerabilities in source code through static analysis +# - Code injection vulnerabilities +# - Authentication/authorization issues +# - Cryptographic weaknesses +# - Data exposure risks +# - OWASP Top 10 detection +# +# Supported Languages: C#/.NET, JavaScript/TypeScript, Python, YAML, Dockerfile +# +# PLACEHOLDER: Choose your SAST scanner implementation below +# Options: +# 1. Semgrep - Fast, open-source, good .NET support +# 2. CodeQL - GitHub's analysis engine +# 3. SonarQube - Enterprise-grade with dashboards +# 4. Snyk Code - Commercial with good accuracy + +name: SAST Scanning + +on: + push: + branches: [main, develop] + paths: + - 'src/**' + - '*.csproj' + - '*.cs' + - '*.ts' + - '*.js' + - '*.py' + - 'Dockerfile*' + pull_request: + paths: + - 'src/**' + - '*.csproj' + - '*.cs' + - '*.ts' + - '*.js' + - '*.py' + - 'Dockerfile*' + schedule: + - cron: '30 3 * * 1' # Weekly on Monday at 3:30 AM UTC + workflow_dispatch: + inputs: + scan_level: + description: 'Scan thoroughness level' + type: choice + options: + - quick + - standard + - comprehensive + default: standard + fail_on_findings: + description: 'Fail workflow on findings' + type: boolean + default: true + +env: + DOTNET_VERSION: '10.0.100' + TZ: UTC + +jobs: + # =========================================================================== + # PLACEHOLDER SAST IMPLEMENTATION + # =========================================================================== + # + # IMPORTANT: Configure your preferred SAST tool by uncommenting ONE of the + # implementation options below. Each option includes the necessary steps + # and configuration for that specific tool. + # + # =========================================================================== + + sast-scan: + name: SAST Analysis + runs-on: ubuntu-22.04 + timeout-minutes: 30 + permissions: + security-events: write + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # ========================================================================= + # PLACEHOLDER: Uncomment your preferred SAST tool configuration + # ========================================================================= + + - name: SAST Scan Placeholder + run: | + echo "::notice::SAST scanning placeholder - configure your scanner below" + echo "" + echo "Available SAST options:" + echo "" + echo "1. SEMGREP (Recommended for open-source)" + echo " Uncomment the Semgrep section below" + echo " - Fast, accurate, good .NET support" + echo " - Free for open-source projects" + echo "" + echo "2. CODEQL (GitHub native)" + echo " Uncomment the CodeQL section below" + echo " - Deep analysis capabilities" + echo " - Native GitHub integration" + echo "" + echo "3. SONARQUBE (Enterprise)" + echo " Uncomment the SonarQube section below" + echo " - Comprehensive dashboards" + echo " - Technical debt tracking" + echo "" + echo "4. SNYK CODE (Commercial)" + echo " Uncomment the Snyk section below" + echo " - High accuracy" + echo " - Good IDE integration" + + # ========================================================================= + # OPTION 1: SEMGREP + # ========================================================================= + # Uncomment the following section to use Semgrep: + # + # - name: Run Semgrep + # uses: returntocorp/semgrep-action@v1 + # with: + # config: >- + # p/default + # p/security-audit + # p/owasp-top-ten + # p/csharp + # p/javascript + # p/typescript + # p/python + # p/docker + # env: + # SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} + + # ========================================================================= + # OPTION 2: CODEQL + # ========================================================================= + # Uncomment the following section to use CodeQL: + # + # - name: Initialize CodeQL + # uses: github/codeql-action/init@v3 + # with: + # languages: csharp, javascript + # queries: security-and-quality + # + # - name: Build for CodeQL + # run: | + # dotnet build src/StellaOps.sln --configuration Release + # + # - name: Perform CodeQL Analysis + # uses: github/codeql-action/analyze@v3 + # with: + # category: "/language:csharp" + + # ========================================================================= + # OPTION 3: SONARQUBE + # ========================================================================= + # Uncomment the following section to use SonarQube: + # + # - name: SonarQube Scan + # uses: SonarSource/sonarqube-scan-action@master + # env: + # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + # with: + # args: > + # -Dsonar.projectKey=stellaops + # -Dsonar.sources=src/ + # -Dsonar.exclusions=**/bin/**,**/obj/**,**/node_modules/** + + # ========================================================================= + # OPTION 4: SNYK CODE + # ========================================================================= + # Uncomment the following section to use Snyk Code: + # + # - name: Setup Snyk + # uses: snyk/actions/setup@master + # + # - name: Snyk Code Test + # run: snyk code test --sarif-file-output=snyk-code.sarif + # env: + # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + # continue-on-error: true + # + # - name: Upload Snyk results + # uses: github/codeql-action/upload-sarif@v3 + # with: + # sarif_file: snyk-code.sarif + + # =========================================================================== + # .NET SECURITY ANALYSIS (built-in) + # =========================================================================== + + dotnet-security: + name: .NET Security Analysis + runs-on: ubuntu-22.04 + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Restore packages + run: dotnet restore src/StellaOps.sln + + - name: Run Security Code Analysis + run: | + # Enable nullable reference types warnings as errors for security + dotnet build src/StellaOps.sln \ + --configuration Release \ + --no-restore \ + /p:TreatWarningsAsErrors=false \ + /p:EnableNETAnalyzers=true \ + /p:AnalysisLevel=latest \ + /warnaserror:CA2100,CA2109,CA2119,CA2153,CA2300,CA2301,CA2302,CA2305,CA2310,CA2311,CA2312,CA2315,CA2321,CA2322,CA2326,CA2327,CA2328,CA2329,CA2330,CA2350,CA2351,CA2352,CA2353,CA2354,CA2355,CA2356,CA2361,CA2362,CA3001,CA3002,CA3003,CA3004,CA3005,CA3006,CA3007,CA3008,CA3009,CA3010,CA3011,CA3012,CA3061,CA3075,CA3076,CA3077,CA3147,CA5350,CA5351,CA5358,CA5359,CA5360,CA5361,CA5362,CA5363,CA5364,CA5365,CA5366,CA5367,CA5368,CA5369,CA5370,CA5371,CA5372,CA5373,CA5374,CA5375,CA5376,CA5377,CA5378,CA5379,CA5380,CA5381,CA5382,CA5383,CA5384,CA5385,CA5386,CA5387,CA5388,CA5389,CA5390,CA5391,CA5392,CA5393,CA5394,CA5395,CA5396,CA5397,CA5398,CA5399,CA5400,CA5401,CA5402,CA5403 \ + 2>&1 | tee build-security.log || true + + - name: Parse security warnings + run: | + echo "### .NET Security Analysis" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Count security warnings + SECURITY_WARNINGS=$(grep -E "warning CA[235][0-9]{3}" build-security.log | wc -l || echo "0") + echo "- Security warnings found: $SECURITY_WARNINGS" >> $GITHUB_STEP_SUMMARY + + if [[ $SECURITY_WARNINGS -gt 0 ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "
Security Warnings" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + grep -E "warning CA[235][0-9]{3}" build-security.log | head -50 >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + fi + + - name: Upload security log + uses: actions/upload-artifact@v4 + if: always() + with: + name: sast-dotnet-security-log + path: build-security.log + retention-days: 14 + + # =========================================================================== + # DEPENDENCY VULNERABILITY CHECK + # =========================================================================== + + dependency-check: + name: Dependency Vulnerabilities + runs-on: ubuntu-22.04 + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Run vulnerability audit + run: | + echo "### Dependency Vulnerability Audit" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check for known vulnerabilities in NuGet packages + dotnet list src/StellaOps.sln package --vulnerable --include-transitive 2>&1 | tee vuln-report.txt || true + + # Parse results + VULN_COUNT=$(grep -c "has the following vulnerable packages" vuln-report.txt || echo "0") + + if [[ $VULN_COUNT -gt 0 ]]; then + echo "::warning::Found $VULN_COUNT projects with vulnerable dependencies" + echo "- Projects with vulnerabilities: $VULN_COUNT" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "
Vulnerability Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat vuln-report.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + else + echo "No known vulnerabilities found in dependencies." >> $GITHUB_STEP_SUMMARY + fi + + - name: Upload vulnerability report + uses: actions/upload-artifact@v4 + if: always() + with: + name: sast-vulnerability-report + path: vuln-report.txt + retention-days: 14 + + # =========================================================================== + # DOCKERFILE SECURITY LINTING + # =========================================================================== + + dockerfile-lint: + name: Dockerfile Security + runs-on: ubuntu-22.04 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Find Dockerfiles + id: find + run: | + DOCKERFILES=$(find . -name "Dockerfile*" -type f ! -path "./node_modules/*" | jq -R -s -c 'split("\n") | map(select(length > 0))') + COUNT=$(echo "$DOCKERFILES" | jq 'length') + echo "files=$DOCKERFILES" >> $GITHUB_OUTPUT + echo "count=$COUNT" >> $GITHUB_OUTPUT + echo "Found $COUNT Dockerfiles" + + - name: Install Hadolint + if: steps.find.outputs.count != '0' + run: | + wget -qO hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64 + chmod +x hadolint + sudo mv hadolint /usr/local/bin/ + + - name: Lint Dockerfiles + if: steps.find.outputs.count != '0' + run: | + echo "### Dockerfile Security Lint" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + TOTAL_ISSUES=0 + + for dockerfile in $(echo '${{ steps.find.outputs.files }}' | jq -r '.[]'); do + echo "Linting: $dockerfile" + ISSUES=$(hadolint --format json "$dockerfile" 2>/dev/null || echo "[]") + ISSUE_COUNT=$(echo "$ISSUES" | jq 'length') + TOTAL_ISSUES=$((TOTAL_ISSUES + ISSUE_COUNT)) + + if [[ $ISSUE_COUNT -gt 0 ]]; then + echo "- **$dockerfile**: $ISSUE_COUNT issues" >> $GITHUB_STEP_SUMMARY + fi + done + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Total issues found: $TOTAL_ISSUES**" >> $GITHUB_STEP_SUMMARY + + if [[ $TOTAL_ISSUES -gt 0 ]] && [[ "${{ github.event.inputs.fail_on_findings }}" == "true" ]]; then + echo "::warning::Found $TOTAL_ISSUES Dockerfile security issues" + fi + + # =========================================================================== + # SUMMARY + # =========================================================================== + + summary: + name: SAST Summary + runs-on: ubuntu-22.04 + needs: [sast-scan, dotnet-security, dependency-check, dockerfile-lint] + if: always() + steps: + - name: Generate summary + run: | + echo "## SAST Scan Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| SAST Analysis | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| .NET Security | ${{ needs.dotnet-security.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dependency Check | ${{ needs.dependency-check.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dockerfile Lint | ${{ needs.dockerfile-lint.result }} |" >> $GITHUB_STEP_SUMMARY + + - name: Check for failures + if: | + github.event.inputs.fail_on_findings == 'true' && + (needs.sast-scan.result == 'failure' || + needs.dotnet-security.result == 'failure' || + needs.dependency-check.result == 'failure') + run: exit 1 diff --git a/.gitea/workflows/secrets-scan.yml b/.gitea/workflows/secrets-scan.yml new file mode 100644 index 000000000..05d2c240d --- /dev/null +++ b/.gitea/workflows/secrets-scan.yml @@ -0,0 +1,105 @@ +# Secrets Scanning Workflow +# Sprint: CI/CD Enhancement - Security Scanning +# +# Purpose: Detect hardcoded secrets, API keys, and credentials in code +# Triggers: Push to main/develop, all PRs +# +# Tool: PLACEHOLDER - Choose one: TruffleHog, Gitleaks, or Semgrep + +name: Secrets Scanning + +on: + push: + branches: [main, develop] + pull_request: + workflow_dispatch: + inputs: + scan_history: + description: 'Scan full git history' + required: false + type: boolean + default: false + +jobs: + secrets-scan: + name: Scan for Secrets + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: ${{ github.event.inputs.scan_history == 'true' && 0 || 50 }} + + # PLACEHOLDER: Choose your secrets scanner + # Option 1: TruffleHog (recommended - comprehensive, low false positives) + # Option 2: Gitleaks (fast, good for CI) + # Option 3: Semgrep (if already using for SAST) + + - name: TruffleHog Scan + id: trufflehog + # Uncomment when ready to use TruffleHog: + # uses: trufflesecurity/trufflehog@main + # with: + # extra_args: --only-verified + run: | + echo "::notice::Secrets scanning placeholder - configure scanner below" + echo "" + echo "Available options:" + echo " 1. TruffleHog: trufflesecurity/trufflehog@main" + echo " 2. Gitleaks: gitleaks/gitleaks-action@v2" + echo " 3. Semgrep: returntocorp/semgrep-action@v1" + echo "" + echo "To enable, uncomment the appropriate action above" + + # Alternative: Gitleaks + # - name: Gitleaks Scan + # uses: gitleaks/gitleaks-action@v2 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + + # Alternative: Semgrep (secrets rules) + # - name: Semgrep Secrets Scan + # uses: returntocorp/semgrep-action@v1 + # with: + # config: p/secrets + + - name: Upload scan results + if: always() + uses: actions/upload-artifact@v4 + with: + name: secrets-scan-results + path: | + **/trufflehog-*.json + **/gitleaks-*.json + **/semgrep-*.json + retention-days: 30 + if-no-files-found: ignore + + summary: + name: Scan Summary + runs-on: ubuntu-latest + needs: [secrets-scan] + if: always() + + steps: + - name: Generate summary + run: | + echo "## Secrets Scanning Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.secrets-scan.result }}" == "success" ]]; then + echo "### No secrets detected" >> $GITHUB_STEP_SUMMARY + elif [[ "${{ needs.secrets-scan.result }}" == "failure" ]]; then + echo "### Secrets detected - review required" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Please review the scan artifacts for details." >> $GITHUB_STEP_SUMMARY + else + echo "### Scan status: ${{ needs.secrets-scan.result }}" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Scanner:** Placeholder (configure in workflow)" >> $GITHUB_STEP_SUMMARY + echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/service-release.yml b/.gitea/workflows/service-release.yml new file mode 100644 index 000000000..61c848021 --- /dev/null +++ b/.gitea/workflows/service-release.yml @@ -0,0 +1,490 @@ +# Service Release Pipeline +# Sprint: CI/CD Enhancement - Per-Service Auto-Versioning +# +# Purpose: Automated per-service release pipeline with semantic versioning +# and Docker tag format: {semver}+{YYYYMMDDHHmmss} +# +# Triggers: +# - Tag: service-{name}-v{semver} (e.g., service-scanner-v1.2.3) +# - Manual dispatch with service selection and bump type + +name: Service Release + +on: + push: + tags: + - 'service-*-v*' + workflow_dispatch: + inputs: + service: + description: 'Service to release' + required: true + type: choice + options: + - authority + - attestor + - concelier + - scanner + - policy + - signer + - excititor + - gateway + - scheduler + - cli + - orchestrator + - notify + - sbomservice + - vexhub + - evidencelocker + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + default: 'patch' + dry_run: + description: 'Dry run (no actual release)' + required: false + type: boolean + default: false + skip_tests: + description: 'Skip tests (use with caution)' + required: false + type: boolean + default: false + +env: + DOTNET_VERSION: '10.0.100' + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + REGISTRY: git.stella-ops.org/stella-ops.org + SYFT_VERSION: '1.21.0' + +jobs: + # =========================================================================== + # Parse tag or manual inputs to determine service and version + # =========================================================================== + resolve: + name: Resolve Release Parameters + runs-on: ubuntu-latest + outputs: + service: ${{ steps.resolve.outputs.service }} + bump_type: ${{ steps.resolve.outputs.bump_type }} + current_version: ${{ steps.resolve.outputs.current_version }} + new_version: ${{ steps.resolve.outputs.new_version }} + docker_tag: ${{ steps.resolve.outputs.docker_tag }} + is_dry_run: ${{ steps.resolve.outputs.is_dry_run }} + skip_tests: ${{ steps.resolve.outputs.skip_tests }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Resolve parameters + id: resolve + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + # Parse tag: service-{name}-v{version} + TAG="${GITHUB_REF#refs/tags/}" + echo "Processing tag: $TAG" + + if [[ "$TAG" =~ ^service-([a-z]+)-v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + SERVICE="${BASH_REMATCH[1]}" + VERSION="${BASH_REMATCH[2]}" + BUMP_TYPE="explicit" + else + echo "::error::Invalid tag format: $TAG (expected: service-{name}-v{semver})" + exit 1 + fi + + IS_DRY_RUN="false" + SKIP_TESTS="false" + else + # Manual dispatch + SERVICE="${{ github.event.inputs.service }}" + BUMP_TYPE="${{ github.event.inputs.bump_type }}" + VERSION="" # Will be calculated + IS_DRY_RUN="${{ github.event.inputs.dry_run }}" + SKIP_TESTS="${{ github.event.inputs.skip_tests }}" + fi + + # Read current version + CURRENT_VERSION=$(.gitea/scripts/release/read-service-version.sh "$SERVICE") + echo "Current version: $CURRENT_VERSION" + + # Calculate new version + if [[ -n "$VERSION" ]]; then + NEW_VERSION="$VERSION" + else + NEW_VERSION=$(python3 .gitea/scripts/release/bump-service-version.py "$SERVICE" "$BUMP_TYPE" --output-version) + fi + echo "New version: $NEW_VERSION" + + # Generate Docker tag + DOCKER_TAG=$(.gitea/scripts/release/generate-docker-tag.sh --version "$NEW_VERSION") + echo "Docker tag: $DOCKER_TAG" + + # Set outputs + echo "service=$SERVICE" >> $GITHUB_OUTPUT + echo "bump_type=$BUMP_TYPE" >> $GITHUB_OUTPUT + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "docker_tag=$DOCKER_TAG" >> $GITHUB_OUTPUT + echo "is_dry_run=$IS_DRY_RUN" >> $GITHUB_OUTPUT + echo "skip_tests=$SKIP_TESTS" >> $GITHUB_OUTPUT + + - name: Summary + run: | + echo "## Release Parameters" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Service | ${{ steps.resolve.outputs.service }} |" >> $GITHUB_STEP_SUMMARY + echo "| Current Version | ${{ steps.resolve.outputs.current_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| New Version | ${{ steps.resolve.outputs.new_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| Docker Tag | ${{ steps.resolve.outputs.docker_tag }} |" >> $GITHUB_STEP_SUMMARY + echo "| Dry Run | ${{ steps.resolve.outputs.is_dry_run }} |" >> $GITHUB_STEP_SUMMARY + + # =========================================================================== + # Update version in source files + # =========================================================================== + update-version: + name: Update Version + runs-on: ubuntu-latest + needs: [resolve] + if: needs.resolve.outputs.is_dry_run != 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITEA_TOKEN }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Update version + run: | + python3 .gitea/scripts/release/bump-service-version.py \ + "${{ needs.resolve.outputs.service }}" \ + "${{ needs.resolve.outputs.new_version }}" \ + --docker-tag "${{ needs.resolve.outputs.docker_tag }}" \ + --git-sha "${{ github.sha }}" + + - name: Commit version update + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add src/Directory.Versions.props devops/releases/service-versions.json + + if git diff --cached --quiet; then + echo "No version changes to commit" + else + git commit -m "chore(${{ needs.resolve.outputs.service }}): release v${{ needs.resolve.outputs.new_version }} + + Docker tag: ${{ needs.resolve.outputs.docker_tag }} + + 🤖 Generated with [Claude Code](https://claude.com/claude-code) + + Co-Authored-By: github-actions[bot] " + + git push + fi + + # =========================================================================== + # Build and test the service + # =========================================================================== + build-test: + name: Build and Test + runs-on: ubuntu-latest + needs: [resolve, update-version] + if: always() && (needs.update-version.result == 'success' || needs.update-version.result == 'skipped') + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Restore dependencies + run: dotnet restore src/StellaOps.sln + + - name: Build solution + run: | + dotnet build src/StellaOps.sln \ + --configuration Release \ + --no-restore \ + -p:StellaOpsServiceVersion=${{ needs.resolve.outputs.new_version }} + + - name: Run tests + if: needs.resolve.outputs.skip_tests != 'true' + run: | + SERVICE="${{ needs.resolve.outputs.service }}" + SERVICE_PASCAL=$(echo "$SERVICE" | sed -r 's/(^|-)(\w)/\U\2/g') + + # Find and run tests for this service + TEST_PROJECTS=$(find src -path "*/${SERVICE_PASCAL}/*" -name "*.Tests.csproj" -o -path "*/${SERVICE_PASCAL}*Tests*" -name "*.csproj" | head -20) + + if [[ -n "$TEST_PROJECTS" ]]; then + echo "Running tests for: $TEST_PROJECTS" + echo "$TEST_PROJECTS" | xargs -I{} dotnet test {} --configuration Release --no-build --verbosity normal + else + echo "::warning::No test projects found for service: $SERVICE" + fi + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: build-${{ needs.resolve.outputs.service }} + path: | + src/**/bin/Release/**/*.dll + src/**/bin/Release/**/*.exe + src/**/bin/Release/**/*.pdb + retention-days: 7 + + # =========================================================================== + # Build and publish Docker image + # =========================================================================== + publish-container: + name: Publish Container + runs-on: ubuntu-latest + needs: [resolve, build-test] + if: needs.resolve.outputs.is_dry_run != 'true' + outputs: + image_digest: ${{ steps.push.outputs.digest }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Determine Dockerfile path + id: dockerfile + run: | + SERVICE="${{ needs.resolve.outputs.service }}" + SERVICE_PASCAL=$(echo "$SERVICE" | sed -r 's/(^|-)(\w)/\U\2/g') + + # Look for service-specific Dockerfile + DOCKERFILE_PATHS=( + "devops/docker/${SERVICE}/Dockerfile" + "devops/docker/${SERVICE_PASCAL}/Dockerfile" + "src/${SERVICE_PASCAL}/Dockerfile" + "src/${SERVICE_PASCAL}/StellaOps.${SERVICE_PASCAL}.WebService/Dockerfile" + "devops/docker/platform/Dockerfile" + ) + + for path in "${DOCKERFILE_PATHS[@]}"; do + if [[ -f "$path" ]]; then + echo "dockerfile=$path" >> $GITHUB_OUTPUT + echo "Found Dockerfile: $path" + exit 0 + fi + done + + echo "::error::No Dockerfile found for service: $SERVICE" + exit 1 + + - name: Build and push image + id: push + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ steps.dockerfile.outputs.dockerfile }} + push: true + tags: | + ${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}:${{ needs.resolve.outputs.docker_tag }} + ${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}:${{ needs.resolve.outputs.new_version }} + ${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}:latest + labels: | + org.opencontainers.image.title=${{ needs.resolve.outputs.service }} + org.opencontainers.image.version=${{ needs.resolve.outputs.new_version }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + com.stellaops.service.name=${{ needs.resolve.outputs.service }} + com.stellaops.service.version=${{ needs.resolve.outputs.new_version }} + com.stellaops.docker.tag=${{ needs.resolve.outputs.docker_tag }} + build-args: | + VERSION=${{ needs.resolve.outputs.new_version }} + GIT_SHA=${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Image summary + run: | + echo "## Container Image" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Image | \`${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Tag | \`${{ needs.resolve.outputs.docker_tag }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Digest | \`${{ steps.push.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY + + # =========================================================================== + # Generate SBOM + # =========================================================================== + generate-sbom: + name: Generate SBOM + runs-on: ubuntu-latest + needs: [resolve, publish-container] + if: needs.resolve.outputs.is_dry_run != 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Syft + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \ + sh -s -- -b /usr/local/bin v${{ env.SYFT_VERSION }} + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Generate SBOM + run: | + IMAGE="${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}:${{ needs.resolve.outputs.docker_tag }}" + + syft "$IMAGE" \ + --output cyclonedx-json=sbom.cyclonedx.json \ + --output spdx-json=sbom.spdx.json + + echo "Generated SBOMs for: $IMAGE" + + - name: Upload SBOM artifacts + uses: actions/upload-artifact@v4 + with: + name: sbom-${{ needs.resolve.outputs.service }}-${{ needs.resolve.outputs.new_version }} + path: | + sbom.cyclonedx.json + sbom.spdx.json + retention-days: 90 + + # =========================================================================== + # Sign artifacts with Cosign + # =========================================================================== + sign-artifacts: + name: Sign Artifacts + runs-on: ubuntu-latest + needs: [resolve, publish-container, generate-sbom] + if: needs.resolve.outputs.is_dry_run != 'true' + + steps: + - name: Install Cosign + uses: sigstore/cosign-installer@v3 + + - name: Login to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Sign container image + if: env.COSIGN_PRIVATE_KEY_B64 != '' + env: + COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + echo "$COSIGN_PRIVATE_KEY_B64" | base64 -d > cosign.key + + IMAGE="${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}@${{ needs.publish-container.outputs.image_digest }}" + + cosign sign --key cosign.key \ + -a "service=${{ needs.resolve.outputs.service }}" \ + -a "version=${{ needs.resolve.outputs.new_version }}" \ + -a "docker-tag=${{ needs.resolve.outputs.docker_tag }}" \ + "$IMAGE" + + rm -f cosign.key + echo "Signed: $IMAGE" + + - name: Download SBOM + uses: actions/download-artifact@v4 + with: + name: sbom-${{ needs.resolve.outputs.service }}-${{ needs.resolve.outputs.new_version }} + path: sbom/ + + - name: Attach SBOM to image + if: env.COSIGN_PRIVATE_KEY_B64 != '' + env: + COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + echo "$COSIGN_PRIVATE_KEY_B64" | base64 -d > cosign.key + + IMAGE="${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}@${{ needs.publish-container.outputs.image_digest }}" + + cosign attach sbom --sbom sbom/sbom.cyclonedx.json "$IMAGE" + cosign sign --key cosign.key --attachment sbom "$IMAGE" + + rm -f cosign.key + + # =========================================================================== + # Release summary + # =========================================================================== + summary: + name: Release Summary + runs-on: ubuntu-latest + needs: [resolve, build-test, publish-container, generate-sbom, sign-artifacts] + if: always() + + steps: + - name: Generate summary + run: | + echo "# Service Release: ${{ needs.resolve.outputs.service }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Release Details" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Service | ${{ needs.resolve.outputs.service }} |" >> $GITHUB_STEP_SUMMARY + echo "| Version | ${{ needs.resolve.outputs.new_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| Previous | ${{ needs.resolve.outputs.current_version }} |" >> $GITHUB_STEP_SUMMARY + echo "| Docker Tag | \`${{ needs.resolve.outputs.docker_tag }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Git SHA | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "## Job Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Build & Test | ${{ needs.build-test.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Publish Container | ${{ needs.publish-container.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Generate SBOM | ${{ needs.generate-sbom.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| Sign Artifacts | ${{ needs.sign-artifacts.result }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.resolve.outputs.is_dry_run }}" == "true" ]]; then + echo "⚠️ **This was a dry run. No artifacts were published.**" >> $GITHUB_STEP_SUMMARY + else + echo "## Pull Image" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.REGISTRY }}/${{ needs.resolve.outputs.service }}:${{ needs.resolve.outputs.docker_tag }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitea/workflows/templates/replay-verify.yml b/.gitea/workflows/templates/replay-verify.yml new file mode 100644 index 000000000..7258a7817 --- /dev/null +++ b/.gitea/workflows/templates/replay-verify.yml @@ -0,0 +1,267 @@ +# ============================================================================= +# replay-verify.yml +# Sprint: SPRINT_20251228_001_BE_replay_manifest_ci (T4) +# Description: CI workflow template for SBOM hash drift detection +# ============================================================================= +# +# This workflow verifies that SBOM generation and verdict computation are +# deterministic by comparing replay manifest hashes across builds. +# +# Usage: +# 1. Copy this template to your project's .gitea/workflows/ directory +# 2. Adjust the image name and scan parameters as needed +# 3. Optionally enable the SBOM attestation step +# +# Exit codes: +# 0 - Verification passed, all hashes match +# 1 - Drift detected, hashes differ +# 2 - Verification error (missing inputs, invalid manifest) +# +# ============================================================================= + +name: SBOM Replay Verification + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + fail_on_drift: + description: 'Fail build if hash drift detected' + required: false + default: 'true' + type: boolean + strict_mode: + description: 'Enable strict verification mode' + required: false + default: 'false' + type: boolean + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + STELLAOPS_VERSION: '1.0.0' + +jobs: + build-and-scan: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write # For OIDC-based signing + + outputs: + image_digest: ${{ steps.build.outputs.digest }} + sbom_digest: ${{ steps.scan.outputs.sbom_digest }} + verdict_digest: ${{ steps.scan.outputs.verdict_digest }} + replay_manifest: ${{ steps.scan.outputs.replay_manifest }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to container registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix= + type=ref,event=branch + type=ref,event=pr + + - name: Build and push image + id: build + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: false # We generate our own SBOM + + - name: Install StellaOps CLI + run: | + curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }} + echo "$HOME/.stellaops/bin" >> $GITHUB_PATH + + - name: Scan image and generate replay manifest + id: scan + env: + IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }} + run: | + # Scan image with StellaOps + stella scan \ + --image "${IMAGE_REF}" \ + --output-sbom sbom.json \ + --output-findings findings.json \ + --output-verdict verdict.json \ + --format cyclonedx-1.6 + + # Export replay manifest for CI verification + stella replay export \ + --image "${IMAGE_REF}" \ + --output replay.json \ + --include-feeds \ + --include-reachability \ + --pretty + + # Extract digests for outputs + SBOM_DIGEST=$(sha256sum sbom.json | cut -d' ' -f1) + VERDICT_DIGEST=$(sha256sum verdict.json | cut -d' ' -f1) + + echo "sbom_digest=sha256:${SBOM_DIGEST}" >> $GITHUB_OUTPUT + echo "verdict_digest=sha256:${VERDICT_DIGEST}" >> $GITHUB_OUTPUT + echo "replay_manifest=replay.json" >> $GITHUB_OUTPUT + + # Display summary + echo "### Scan Results" >> $GITHUB_STEP_SUMMARY + echo "| Artifact | Digest |" >> $GITHUB_STEP_SUMMARY + echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Image | \`${{ steps.build.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| SBOM | \`sha256:${SBOM_DIGEST}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Verdict | \`sha256:${VERDICT_DIGEST}\` |" >> $GITHUB_STEP_SUMMARY + + - name: Upload scan artifacts + uses: actions/upload-artifact@v4 + with: + name: scan-artifacts-${{ github.sha }} + path: | + sbom.json + findings.json + verdict.json + replay.json + retention-days: 30 + + verify-determinism: + runs-on: ubuntu-latest + needs: build-and-scan + + steps: + - name: Download scan artifacts + uses: actions/download-artifact@v4 + with: + name: scan-artifacts-${{ github.sha }} + + - name: Install StellaOps CLI + run: | + curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }} + echo "$HOME/.stellaops/bin" >> $GITHUB_PATH + + - name: Verify SBOM determinism + id: verify + env: + FAIL_ON_DRIFT: ${{ inputs.fail_on_drift || 'true' }} + STRICT_MODE: ${{ inputs.strict_mode || 'false' }} + run: | + # Build verification flags + VERIFY_FLAGS="--manifest replay.json" + if [ "${FAIL_ON_DRIFT}" = "true" ]; then + VERIFY_FLAGS="${VERIFY_FLAGS} --fail-on-drift" + fi + if [ "${STRICT_MODE}" = "true" ]; then + VERIFY_FLAGS="${VERIFY_FLAGS} --strict-mode" + fi + + # Run verification + stella replay export verify ${VERIFY_FLAGS} + EXIT_CODE=$? + + # Report results + if [ $EXIT_CODE -eq 0 ]; then + echo "✅ Verification passed - all hashes match" >> $GITHUB_STEP_SUMMARY + echo "status=success" >> $GITHUB_OUTPUT + elif [ $EXIT_CODE -eq 1 ]; then + echo "⚠️ Drift detected - hashes differ from expected" >> $GITHUB_STEP_SUMMARY + echo "status=drift" >> $GITHUB_OUTPUT + else + echo "❌ Verification error" >> $GITHUB_STEP_SUMMARY + echo "status=error" >> $GITHUB_OUTPUT + fi + + exit $EXIT_CODE + + - name: Comment on PR (on drift) + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## ⚠️ SBOM Determinism Check Failed + + Hash drift detected between scan runs. This may indicate non-deterministic build or scan behavior. + + **Expected digests:** + - SBOM: \`${{ needs.build-and-scan.outputs.sbom_digest }}\` + - Verdict: \`${{ needs.build-and-scan.outputs.verdict_digest }}\` + + **Possible causes:** + - Non-deterministic build artifacts (timestamps, random values) + - Changed dependencies between runs + - Environment differences + + **Next steps:** + 1. Review the replay manifest in the artifacts + 2. Check build logs for non-deterministic elements + 3. Consider using \`--strict-mode\` for detailed drift analysis` + }) + + # Optional: Attest SBOM to OCI registry + attest-sbom: + runs-on: ubuntu-latest + needs: [build-and-scan, verify-determinism] + if: github.event_name != 'pull_request' && success() + permissions: + packages: write + id-token: write + + steps: + - name: Download scan artifacts + uses: actions/download-artifact@v4 + with: + name: scan-artifacts-${{ github.sha }} + + - name: Install StellaOps CLI + run: | + curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }} + echo "$HOME/.stellaops/bin" >> $GITHUB_PATH + + - name: Log in to container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Attach SBOM attestation + env: + IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-and-scan.outputs.image_digest }} + run: | + # Sign and attach SBOM as in-toto attestation + stella attest attach \ + --image "${IMAGE_REF}" \ + --sbom sbom.json \ + --predicate-type https://cyclonedx.org/bom/v1.6 \ + --sign keyless + + echo "### SBOM Attestation" >> $GITHUB_STEP_SUMMARY + echo "SBOM attached to \`${IMAGE_REF}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/test-matrix.yml b/.gitea/workflows/test-matrix.yml index ffc61c952..d79e87dde 100644 --- a/.gitea/workflows/test-matrix.yml +++ b/.gitea/workflows/test-matrix.yml @@ -1,9 +1,10 @@ # .gitea/workflows/test-matrix.yml # Unified test matrix pipeline with TRX reporting for all test categories # Sprint: SPRINT_20251226_007_CICD - Dynamic test discovery +# Refactored: SPRINT_CICD_Enhancement - DRY principle, matrix strategy # -# WORKFLOW INTEGRATION STRATEGY (Sprint 20251226_003_CICD): -# ========================================================= +# WORKFLOW INTEGRATION STRATEGY: +# ============================== # This workflow is the PRIMARY test execution workflow for PR gating. # It dynamically discovers and runs ALL test projects by Category trait. # @@ -12,8 +13,6 @@ # # Scheduled/On-Demand Categories: # Performance, Benchmark, AirGap, Chaos, Determinism, Resilience, Observability -# -# For build/deploy operations, see: build-test-deploy.yml (runs in parallel) name: Test Matrix @@ -85,10 +84,6 @@ jobs: - name: Find all test projects id: find run: | - # Find all test project files, including non-standard naming conventions: - # - *.Tests.csproj (standard) - # - *UnitTests.csproj, *SmokeTests.csproj, *FixtureTests.csproj, *IntegrationTests.csproj - # Exclude: TestKit, Testing libraries, node_modules, bin, obj PROJECTS=$(find src \( \ -name "*.Tests.csproj" \ -o -name "*UnitTests.csproj" \ @@ -104,11 +99,9 @@ jobs: ! -name "*Testing.csproj" \ | sort) - # Count projects COUNT=$(echo "$PROJECTS" | grep -c '.csproj' || echo "0") echo "Found $COUNT test projects" - # Output as JSON array for matrix echo "projects=$(echo "$PROJECTS" | jq -R -s -c 'split("\n") | map(select(length > 0))')" >> $GITHUB_OUTPUT echo "count=$COUNT" >> $GITHUB_OUTPUT @@ -122,13 +115,34 @@ jobs: # =========================================================================== # PR-GATING TESTS (run on every push/PR) + # Uses matrix strategy to run all categories in parallel # =========================================================================== - unit: - name: Unit Tests + pr-gating-tests: + name: ${{ matrix.category }} Tests runs-on: ubuntu-22.04 - timeout-minutes: 20 + timeout-minutes: ${{ matrix.timeout }} needs: discover + strategy: + fail-fast: false + matrix: + include: + - category: Unit + timeout: 20 + collect_coverage: true + - category: Architecture + timeout: 15 + collect_coverage: false + - category: Contract + timeout: 15 + collect_coverage: false + - category: Security + timeout: 25 + collect_coverage: false + - category: Golden + timeout: 25 + collect_coverage: false + steps: - name: Checkout uses: actions/checkout@v4 @@ -141,165 +155,26 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - - name: Run Unit Tests (all test projects) + - name: Run ${{ matrix.category }} Tests run: | - mkdir -p ./TestResults/Unit - FAILED=0 - PASSED=0 - SKIPPED=0 - - # Find and run all test projects with Unit category - # Use expanded pattern to include non-standard naming conventions - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - - # Create unique TRX filename using path hash to avoid duplicates - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-unit.trx - - # Restore and build in one step, then test - if dotnet test "$proj" \ - --filter "Category=Unit" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Unit \ - --collect:"XPlat Code Coverage" \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - echo "✓ $proj passed" - else - # Check if it was just "no tests matched" which is not a failure - if [ $? -eq 0 ] || grep -q "No test matches" /tmp/test-output.txt 2>/dev/null; then - SKIPPED=$((SKIPPED + 1)) - echo "○ $proj skipped (no Unit tests)" - else - FAILED=$((FAILED + 1)) - echo "✗ $proj failed" - fi - fi - echo "::endgroup::" - done - - echo "## Unit Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Failed: $FAILED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - # Fail if any tests failed - if [ $FAILED -gt 0 ]; then - exit 1 + chmod +x .gitea/scripts/test/run-test-category.sh + if [[ "${{ matrix.collect_coverage }}" == "true" ]]; then + .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}" --collect-coverage + else + .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}" fi - name: Upload Test Results uses: actions/upload-artifact@v4 if: always() with: - name: test-results-unit - path: ./TestResults/Unit + name: test-results-${{ matrix.category }} + path: ./TestResults/${{ matrix.category }} retention-days: 14 - architecture: - name: Architecture Tests - runs-on: ubuntu-22.04 - timeout-minutes: 15 - needs: discover - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Architecture Tests (all test projects) - run: | - mkdir -p ./TestResults/Architecture - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-architecture.trx - if dotnet test "$proj" \ - --filter "Category=Architecture" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Architecture \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Architecture Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-architecture - path: ./TestResults/Architecture - retention-days: 14 - - contract: - name: Contract Tests - runs-on: ubuntu-22.04 - timeout-minutes: 15 - needs: discover - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Contract Tests (all test projects) - run: | - mkdir -p ./TestResults/Contract - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-contract.trx - if dotnet test "$proj" \ - --filter "Category=Contract" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Contract \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Contract Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-contract - path: ./TestResults/Contract - retention-days: 14 + # =========================================================================== + # INTEGRATION TESTS (separate due to service dependency) + # =========================================================================== integration: name: Integration Tests @@ -332,520 +207,112 @@ jobs: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - - name: Run Integration Tests (all test projects) + - name: Run Integration Tests env: STELLAOPS_TEST_POSTGRES_CONNECTION: "Host=localhost;Port=5432;Database=stellaops_test;Username=stellaops;Password=stellaops" run: | - mkdir -p ./TestResults/Integration - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-integration.trx - if dotnet test "$proj" \ - --filter "Category=Integration" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Integration \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Integration Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY + chmod +x .gitea/scripts/test/run-test-category.sh + .gitea/scripts/test/run-test-category.sh Integration - name: Upload Test Results uses: actions/upload-artifact@v4 if: always() with: - name: test-results-integration + name: test-results-Integration path: ./TestResults/Integration retention-days: 14 - security: - name: Security Tests - runs-on: ubuntu-22.04 - timeout-minutes: 25 - needs: discover - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Security Tests (all test projects) - run: | - mkdir -p ./TestResults/Security - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-security.trx - if dotnet test "$proj" \ - --filter "Category=Security" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Security \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Security Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-security - path: ./TestResults/Security - retention-days: 14 - - golden: - name: Golden Tests - runs-on: ubuntu-22.04 - timeout-minutes: 25 - needs: discover - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Golden Tests (all test projects) - run: | - mkdir -p ./TestResults/Golden - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-golden.trx - if dotnet test "$proj" \ - --filter "Category=Golden" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Golden \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Golden Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-golden - path: ./TestResults/Golden - retention-days: 14 - # =========================================================================== # SCHEDULED/ON-DEMAND TESTS + # Uses matrix strategy for extended test categories # =========================================================================== - performance: - name: Performance Tests + extended-tests: + name: ${{ matrix.category }} Tests runs-on: ubuntu-22.04 - timeout-minutes: 45 + timeout-minutes: ${{ matrix.timeout }} needs: discover - if: github.event_name == 'schedule' || github.event.inputs.include_performance == 'true' + if: >- + github.event_name == 'schedule' || + github.event.inputs.include_performance == 'true' || + github.event.inputs.include_benchmark == 'true' || + github.event.inputs.include_airgap == 'true' || + github.event.inputs.include_chaos == 'true' || + github.event.inputs.include_determinism == 'true' || + github.event.inputs.include_resilience == 'true' || + github.event.inputs.include_observability == 'true' + strategy: + fail-fast: false + matrix: + include: + - category: Performance + timeout: 45 + trigger_input: include_performance + run_on_schedule: true + - category: Benchmark + timeout: 60 + trigger_input: include_benchmark + run_on_schedule: true + - category: AirGap + timeout: 45 + trigger_input: include_airgap + run_on_schedule: false + - category: Chaos + timeout: 45 + trigger_input: include_chaos + run_on_schedule: false + - category: Determinism + timeout: 45 + trigger_input: include_determinism + run_on_schedule: false + - category: Resilience + timeout: 45 + trigger_input: include_resilience + run_on_schedule: false + - category: Observability + timeout: 30 + trigger_input: include_observability + run_on_schedule: false + steps: + - name: Check if should run + id: should_run + run: | + SHOULD_RUN="false" + if [[ "${{ github.event_name }}" == "schedule" && "${{ matrix.run_on_schedule }}" == "true" ]]; then + SHOULD_RUN="true" + fi + if [[ "${{ github.event.inputs[matrix.trigger_input] }}" == "true" ]]; then + SHOULD_RUN="true" + fi + echo "run=$SHOULD_RUN" >> $GITHUB_OUTPUT + echo "Should run ${{ matrix.category }}: $SHOULD_RUN" + - name: Checkout + if: steps.should_run.outputs.run == 'true' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup .NET + if: steps.should_run.outputs.run == 'true' uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} include-prerelease: true - - name: Run Performance Tests (all test projects) + - name: Run ${{ matrix.category }} Tests + if: steps.should_run.outputs.run == 'true' run: | - mkdir -p ./TestResults/Performance - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-performance.trx - if dotnet test "$proj" \ - --filter "Category=Performance" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Performance \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Performance Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY + chmod +x .gitea/scripts/test/run-test-category.sh + .gitea/scripts/test/run-test-category.sh "${{ matrix.category }}" - name: Upload Test Results uses: actions/upload-artifact@v4 - if: always() + if: always() && steps.should_run.outputs.run == 'true' with: - name: test-results-performance - path: ./TestResults/Performance - retention-days: 14 - - benchmark: - name: Benchmark Tests - runs-on: ubuntu-22.04 - timeout-minutes: 60 - needs: discover - if: github.event_name == 'schedule' || github.event.inputs.include_benchmark == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Benchmark Tests (all test projects) - run: | - mkdir -p ./TestResults/Benchmark - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-benchmark.trx - if dotnet test "$proj" \ - --filter "Category=Benchmark" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Benchmark \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Benchmark Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-benchmark - path: ./TestResults/Benchmark - retention-days: 14 - - airgap: - name: AirGap Tests - runs-on: ubuntu-22.04 - timeout-minutes: 45 - needs: discover - if: github.event.inputs.include_airgap == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run AirGap Tests (all test projects) - run: | - mkdir -p ./TestResults/AirGap - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-airgap.trx - if dotnet test "$proj" \ - --filter "Category=AirGap" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/AirGap \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## AirGap Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-airgap - path: ./TestResults/AirGap - retention-days: 14 - - chaos: - name: Chaos Tests - runs-on: ubuntu-22.04 - timeout-minutes: 45 - needs: discover - if: github.event.inputs.include_chaos == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Chaos Tests (all test projects) - run: | - mkdir -p ./TestResults/Chaos - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-chaos.trx - if dotnet test "$proj" \ - --filter "Category=Chaos" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Chaos \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Chaos Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-chaos - path: ./TestResults/Chaos - retention-days: 14 - - determinism: - name: Determinism Tests - runs-on: ubuntu-22.04 - timeout-minutes: 45 - needs: discover - if: github.event.inputs.include_determinism == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Determinism Tests (all test projects) - run: | - mkdir -p ./TestResults/Determinism - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-determinism.trx - if dotnet test "$proj" \ - --filter "Category=Determinism" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Determinism \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Determinism Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-determinism - path: ./TestResults/Determinism - retention-days: 14 - - resilience: - name: Resilience Tests - runs-on: ubuntu-22.04 - timeout-minutes: 45 - needs: discover - if: github.event.inputs.include_resilience == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Resilience Tests (all test projects) - run: | - mkdir -p ./TestResults/Resilience - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-resilience.trx - if dotnet test "$proj" \ - --filter "Category=Resilience" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Resilience \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Resilience Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-resilience - path: ./TestResults/Resilience - retention-days: 14 - - observability: - name: Observability Tests - runs-on: ubuntu-22.04 - timeout-minutes: 30 - needs: discover - if: github.event.inputs.include_observability == 'true' - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - include-prerelease: true - - - name: Run Observability Tests (all test projects) - run: | - mkdir -p ./TestResults/Observability - FAILED=0 - PASSED=0 - SKIPPED=0 - - for proj in $(find src \( -name "*.Tests.csproj" -o -name "*UnitTests.csproj" -o -name "*SmokeTests.csproj" -o -name "*FixtureTests.csproj" -o -name "*IntegrationTests.csproj" \) -type f ! -path "*/node_modules/*" ! -name "StellaOps.TestKit.csproj" ! -name "*Testing.csproj" | sort); do - echo "::group::Testing $proj" - TRX_NAME=$(echo "$proj" | sed 's|/|_|g' | sed 's|\.csproj||')-observability.trx - if dotnet test "$proj" \ - --filter "Category=Observability" \ - --configuration Release \ - --logger "trx;LogFileName=$TRX_NAME" \ - --results-directory ./TestResults/Observability \ - --verbosity minimal 2>&1; then - PASSED=$((PASSED + 1)) - else - SKIPPED=$((SKIPPED + 1)) - fi - echo "::endgroup::" - done - - echo "## Observability Test Summary" >> $GITHUB_STEP_SUMMARY - echo "- Passed: $PASSED" >> $GITHUB_STEP_SUMMARY - echo "- Skipped: $SKIPPED" >> $GITHUB_STEP_SUMMARY - - - name: Upload Test Results - uses: actions/upload-artifact@v4 - if: always() - with: - name: test-results-observability - path: ./TestResults/Observability + name: test-results-${{ matrix.category }} + path: ./TestResults/${{ matrix.category }} retention-days: 14 # =========================================================================== @@ -855,7 +322,7 @@ jobs: summary: name: Test Summary runs-on: ubuntu-22.04 - needs: [discover, unit, architecture, contract, integration, security, golden] + needs: [discover, pr-gating-tests, integration] if: always() steps: - name: Download all test results @@ -885,18 +352,14 @@ jobs: echo "| Category | Status |" >> $GITHUB_STEP_SUMMARY echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY echo "| Discover | ${{ needs.discover.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Unit | ${{ needs.unit.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Architecture | ${{ needs.architecture.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Contract | ${{ needs.contract.result }} |" >> $GITHUB_STEP_SUMMARY + echo "| PR-Gating Matrix | ${{ needs.pr-gating-tests.result }} |" >> $GITHUB_STEP_SUMMARY echo "| Integration | ${{ needs.integration.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Security | ${{ needs.security.result }} |" >> $GITHUB_STEP_SUMMARY - echo "| Golden | ${{ needs.golden.result }} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Test Projects Discovered: ${{ needs.discover.outputs.test-count }}" >> $GITHUB_STEP_SUMMARY - name: Count TRX files run: | - TRX_COUNT=$(find ./TestResults -name "*.trx" | wc -l) + TRX_COUNT=$(find ./TestResults -name "*.trx" 2>/dev/null | wc -l || echo "0") echo "### Total TRX Files Generated: $TRX_COUNT" >> $GITHUB_STEP_SUMMARY - name: Upload Combined Results diff --git a/.gitignore b/.gitignore index 866df4646..2dcda9936 100644 --- a/.gitignore +++ b/.gitignore @@ -63,13 +63,19 @@ obj/ logs/ tmp/ coverage/ -# Consolidated NuGet cache (all variants) +# Consolidated NuGet cache .nuget/ .nuget-*/ -local-nuget*/ devops/offline/packages/ src/Sdk/StellaOps.Sdk.Generator/tools/jdk-21.0.1+12 # Test artifacts src/__Tests/**/TestResults/ -src/__Tests/__Benchmarks/reachability-benchmark/.jdk/ \ No newline at end of file +src/__Tests/__Benchmarks/reachability-benchmark/.jdk/ + +# Local CI testing secrets (never commit) +devops/ci-local/.env.local +devops/ci-local/.env + +# Act artifacts +out/act-artifacts/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 8e40f1542..1ede1ba39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,41 +81,54 @@ The codebase follows a monorepo pattern with modules under `src/`: | **Core Platform** | | | | Authority | `src/Authority/` | Authentication, authorization, OAuth/OIDC, DPoP | | Gateway | `src/Gateway/` | API gateway with routing and transport abstraction | -| Router | `src/__Libraries/StellaOps.Router.*` | Transport-agnostic messaging (TCP/TLS/UDP/RabbitMQ/Valkey) | +| Router | `src/Router/` | Transport-agnostic messaging (TCP/TLS/UDP/RabbitMQ/Valkey) | | **Data Ingestion** | | | | Concelier | `src/Concelier/` | Vulnerability advisory ingestion and merge engine | | Excititor | `src/Excititor/` | VEX document ingestion and export | | VexLens | `src/VexLens/` | VEX consensus computation across issuers | +| VexHub | `src/VexHub/` | VEX distribution and exchange hub | | IssuerDirectory | `src/IssuerDirectory/` | Issuer trust registry (CSAF publishers) | +| Feedser | `src/Feedser/` | Evidence collection library for backport detection | +| Mirror | `src/Mirror/` | Vulnerability feed mirror and distribution | | **Scanning & Analysis** | | | | Scanner | `src/Scanner/` | Container scanning with SBOM generation (11 language analyzers) | | BinaryIndex | `src/BinaryIndex/` | Binary identity extraction and fingerprinting | | AdvisoryAI | `src/AdvisoryAI/` | AI-assisted advisory analysis | +| ReachGraph | `src/ReachGraph/` | Reachability graph service | +| Symbols | `src/Symbols/` | Symbol resolution and debug information | | **Artifacts & Evidence** | | | | Attestor | `src/Attestor/` | in-toto/DSSE attestation generation | | Signer | `src/Signer/` | Cryptographic signing operations | | SbomService | `src/SbomService/` | SBOM storage, versioning, and lineage ledger | | EvidenceLocker | `src/EvidenceLocker/` | Sealed evidence storage and export | | ExportCenter | `src/ExportCenter/` | Batch export and report generation | -| VexHub | `src/VexHub/` | VEX distribution and exchange hub | +| Provenance | `src/Provenance/` | SLSA/DSSE attestation tooling | | **Policy & Risk** | | | | Policy | `src/Policy/` | Policy engine with K4 lattice logic | +| RiskEngine | `src/RiskEngine/` | Risk scoring runtime with pluggable providers | | VulnExplorer | `src/VulnExplorer/` | Vulnerability exploration and triage UI backend | +| Unknowns | `src/Unknowns/` | Unknown component and symbol tracking | | **Operations** | | | | Scheduler | `src/Scheduler/` | Job scheduling and queue management | | Orchestrator | `src/Orchestrator/` | Workflow orchestration and task coordination | | TaskRunner | `src/TaskRunner/` | Task pack execution engine | -| Notify | `src/Notify/` | Notification delivery (Email, Slack, Teams, Webhooks) | +| Notify | `src/Notify/` | Notification toolkit (Email, Slack, Teams, Webhooks) | +| Notifier | `src/Notifier/` | Notifications Studio host | +| PacksRegistry | `src/PacksRegistry/` | Task packs registry and distribution | +| TimelineIndexer | `src/TimelineIndexer/` | Timeline event indexing | +| Replay | `src/Replay/` | Deterministic replay engine | | **Integration** | | | | CLI | `src/Cli/` | Command-line interface (Native AOT) | | Zastava | `src/Zastava/` | Container registry webhook observer | | Web | `src/Web/` | Angular 17 frontend SPA | +| API | `src/Api/` | OpenAPI contracts and governance | | **Infrastructure** | | | | Cryptography | `src/Cryptography/` | Crypto plugins (FIPS, eIDAS, GOST, SM, PQ) | | Telemetry | `src/Telemetry/` | OpenTelemetry traces, metrics, logging | | Graph | `src/Graph/` | Call graph and reachability data structures | | Signals | `src/Signals/` | Runtime signal collection and correlation | -| Replay | `src/Replay/` | Deterministic replay engine | +| AirGap | `src/AirGap/` | Air-gapped deployment support | +| AOC | `src/Aoc/` | Append-Only Contract enforcement (Roslyn analyzers) | > **Note:** See `docs/modules//architecture.md` for detailed module dossiers. diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 314cf02f1..000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,105 +0,0 @@ - - - - $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)')) - https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json - $([System.IO.Path]::Combine('$(StellaOpsRepoRoot)','NuGet.config')) - - - - - StellaOps - StellaOps - StellaOps - Copyright (c) StellaOps. All rights reserved. - AGPL-3.0-or-later - https://git.stella-ops.org/stella-ops.org/git.stella-ops.org - https://git.stella-ops.org/stella-ops.org/git.stella-ops.org - git - true - README.md - stellaops;security;sbom;vex;attestation;supply-chain - - - - false - $(NoWarn);NU1608;NU1605;NU1202 - $(WarningsNotAsErrors);NU1608;NU1605;NU1202 - $(RestoreNoWarn);NU1608;NU1605;NU1202 - - false - true - clear - clear - clear - clear - clear - clear - true - - - - $(AssetTargetFallback);net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0 - - - - $(DefineConstants);STELLAOPS_CRYPTO_PRO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/StellaOps.Router.slnx b/StellaOps.Router.slnx deleted file mode 100644 index 395a323e6..000000000 --- a/StellaOps.Router.slnx +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/StellaOps.Tests.slnx b/StellaOps.Tests.slnx deleted file mode 100644 index ba788ff0d..000000000 --- a/StellaOps.Tests.slnx +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/build_output_latest.txt b/build_output_latest.txt new file mode 100644 index 000000000..1b2a72964 --- /dev/null +++ b/build_output_latest.txt @@ -0,0 +1,55 @@ + + StellaOps.Router.Common -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Common\bin\Debug\net10.0\StellaOps.Router.Common.dll + StellaOps.Router.Config -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Config\bin\Debug\net10.0\StellaOps.Router.Config.dll + StellaOps.DependencyInjection -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.DependencyInjection\bin\Debug\net10.0\StellaOps.DependencyInjection.dll + StellaOps.Plugin -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Plugin\bin\Debug\net10.0\StellaOps.Plugin.dll + StellaOps.AirGap.Policy -> E:\dev\git.stella-ops.org\src\AirGap\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy\bin\Debug\net10.0\StellaOps.AirGap.Policy.dll + StellaOps.Concelier.SourceIntel -> E:\dev\git.stella-ops.org\src\Concelier\__Libraries\StellaOps.Concelier.SourceIntel\bin\Debug\net10.0\StellaOps.Concelier.SourceIntel.dll + StellaOps.Cryptography -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography\bin\Debug\net10.0\StellaOps.Cryptography.dll + StellaOps.Auth.Abstractions -> E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Auth.Abstractions\bin\Debug\net10.0\StellaOps.Auth.Abstractions.dll + StellaOps.Telemetry.Core -> E:\dev\git.stella-ops.org\src\Telemetry\StellaOps.Telemetry.Core\StellaOps.Telemetry.Core\bin\Debug\net10.0\StellaOps.Telemetry.Core.dll + StellaOps.Canonical.Json -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Canonical.Json\bin\Debug\net10.0\StellaOps.Canonical.Json.dll + StellaOps.Evidence.Bundle -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Evidence.Bundle\bin\Debug\net10.0\StellaOps.Evidence.Bundle.dll + StellaOps.Messaging -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Messaging\bin\Debug\net10.0\StellaOps.Messaging.dll + StellaOps.Router.Transport.Tcp -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Transport.Tcp\bin\Debug\net10.0\StellaOps.Router.Transport.Tcp.dll + StellaOps.Infrastructure.Postgres -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Infrastructure.Postgres\bin\Debug\net10.0\StellaOps.Infrastructure.Postgres.dll + StellaOps.Infrastructure.Postgres.Testing -> E:\dev\git.stella-ops.org\src\__Tests\__Libraries\StellaOps.Infrastructure.Postgres.Testing\bin\Debug\net10.0\StellaOps.Infrastructure.Postgres.Testing.dll + StellaOps.Feedser.BinaryAnalysis -> E:\dev\git.stella-ops.org\src\Feedser\StellaOps.Feedser.BinaryAnalysis\bin\Debug\net10.0\StellaOps.Feedser.BinaryAnalysis.dll + StellaOps.Scheduler.Models -> E:\dev\git.stella-ops.org\src\Scheduler\__Libraries\StellaOps.Scheduler.Models\bin\Debug\net10.0\StellaOps.Scheduler.Models.dll + StellaOps.Aoc -> E:\dev\git.stella-ops.org\src\Aoc\__Libraries\StellaOps.Aoc\bin\Debug\net10.0\StellaOps.Aoc.dll + StellaOps.Router.Transport.RabbitMq -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Transport.RabbitMq\bin\Debug\net10.0\StellaOps.Router.Transport.RabbitMq.dll + NotifySmokeCheck -> E:\dev\git.stella-ops.org\src\Tools\NotifySmokeCheck\bin\Debug\net10.0\NotifySmokeCheck.dll + StellaOps.Infrastructure.EfCore -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Infrastructure.EfCore\bin\Debug\net10.0\StellaOps.Infrastructure.EfCore.dll + StellaOps.Router.Transport.InMemory -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Transport.InMemory\bin\Debug\net10.0\StellaOps.Router.Transport.InMemory.dll + RustFsMigrator -> E:\dev\git.stella-ops.org\src\Tools\RustFsMigrator\bin\Debug\net10.0\RustFsMigrator.dll + StellaOps.Cryptography.Plugin.WineCsp -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.WineCsp\bin\Debug\net10.0\StellaOps.Cryptography.Plugin.WineCsp.dll + StellaOps.Microservice -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Microservice\bin\Debug\net10.0\StellaOps.Microservice.dll + StellaOps.Replay.Core -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Replay.Core\bin\Debug\net10.0\StellaOps.Replay.Core.dll + StellaOps.Messaging.Transport.InMemory -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Messaging.Transport.InMemory\bin\Debug\net10.0\StellaOps.Messaging.Transport.InMemory.dll + StellaOps.Feedser.Core -> E:\dev\git.stella-ops.org\src\Feedser\StellaOps.Feedser.Core\bin\Debug\net10.0\StellaOps.Feedser.Core.dll + StellaOps.Cryptography.Kms -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Kms\bin\Debug\net10.0\StellaOps.Cryptography.Kms.dll + StellaOps.Cryptography.Plugin.PqSoft -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.PqSoft\bin\Debug\net10.0\StellaOps.Cryptography.Plugin.PqSoft.dll + StellaOps.Policy.RiskProfile -> E:\dev\git.stella-ops.org\src\Policy\StellaOps.Policy.RiskProfile\bin\Debug\net10.0\StellaOps.Policy.RiskProfile.dll + StellaOps.Router.Transport.Udp -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Transport.Udp\bin\Debug\net10.0\StellaOps.Router.Transport.Udp.dll + StellaOps.Microservice.SourceGen -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Microservice.SourceGen\bin\Debug\netstandard2.0\StellaOps.Microservice.SourceGen.dll + StellaOps.Findings.Ledger -> E:\dev\git.stella-ops.org\src\Findings\StellaOps.Findings.Ledger\bin\Debug\net10.0\StellaOps.Findings.Ledger.dll + LedgerReplayHarness -> E:\dev\git.stella-ops.org\src\Findings\StellaOps.Findings.Ledger\tools\LedgerReplayHarness\bin\Debug\net10.0\LedgerReplayHarness.dll + StellaOps.Attestor.Envelope -> E:\dev\git.stella-ops.org\src\Attestor\StellaOps.Attestor.Envelope\bin\Debug\net10.0\StellaOps.Attestor.Envelope.dll + StellaOps.Router.Gateway -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.Gateway\bin\Debug\net10.0\StellaOps.Router.Gateway.dll + StellaOps.Ingestion.Telemetry -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Ingestion.Telemetry\bin\Debug\net10.0\StellaOps.Ingestion.Telemetry.dll + Examples.Billing.Microservice -> E:\dev\git.stella-ops.org\src\Router\examples\Examples.Billing.Microservice\bin\Debug\net10.0\Examples.Billing.Microservice.dll + StellaOps.Cryptography.Plugin.Pkcs11Gost -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.Pkcs11Gost\bin\Debug\net10.0\StellaOps.Cryptography.Plugin.Pkcs11Gost.dll + StellaOps.Microservice.AspNetCore -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Microservice.AspNetCore\bin\Debug\net10.0\StellaOps.Microservice.AspNetCore.dll + StellaOps.Router.AspNet -> E:\dev\git.stella-ops.org\src\Router\__Libraries\StellaOps.Router.AspNet\bin\Debug\net10.0\StellaOps.Router.AspNet.dll + StellaOps.Authority.Plugins.Abstractions -> E:\dev\git.stella-ops.org\src\Authority\StellaOps.Authority\StellaOps.Authority.Plugins.Abstractions\bin\Debug\net10.0\StellaOps.Authority.Plugins.Abstractions.dll + StellaOps.Cryptography.Plugin.OfflineVerification -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.OfflineVerification\bin\Debug\net10.0\StellaOps.Cryptography.Plugin.OfflineVerification.dll + Examples.Gateway -> E:\dev\git.stella-ops.org\src\Router\examples\Examples.Gateway\bin\Debug\net10.0\Examples.Gateway.dll + Examples.NotificationService -> E:\dev\git.stella-ops.org\src\Router\examples\Examples.NotificationService\bin\Debug\net10.0\Examples.NotificationService.dll + StellaOps.Provenance.Attestation -> E:\dev\git.stella-ops.org\src\Provenance\StellaOps.Provenance.Attestation\bin\Debug\net10.0\StellaOps.Provenance.Attestation.dll + StellaOps.AirGap.Policy.Analyzers -> E:\dev\git.stella-ops.org\src\AirGap\StellaOps.AirGap.Policy\StellaOps.AirGap.Policy.Analyzers\bin\Debug\netstandard2.0\StellaOps.AirGap.Policy.Analyzers.dll + StellaOps.Cryptography.Plugin.SmRemote -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.Plugin.SmRemote\bin\Debug\net10.0\StellaOps.Cryptography.Plugin.SmRemote.dll + StellaOps.VersionComparison -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.VersionComparison\bin\Debug\net10.0\StellaOps.VersionComparison.dll + StellaOps.TestKit -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.TestKit\bin\Debug\net10.0\StellaOps.TestKit.dll + StellaOps.Aoc.Analyzers -> E:\dev\git.stella-ops.org\src\Aoc\__Analyzers\StellaOps.Aoc.Analyzers\bin\Debug\netstandard2.0\StellaOps.Aoc.Analyzers.dll + StellaOps.AirGap.Importer -> E:\dev\git.stella-ops.org\src\AirGap\StellaOps.AirGap.Importer\bin\Debug\net10.0\StellaOps.AirGap.Importer.dll + StellaOps.Cryptography.PluginLoader -> E:\dev\git.stella-ops.org\src\__Libraries\StellaOps.Cryptography.PluginLoader\bin\Debug\net10.0\StellaOps.Cryptography.PluginLoader.dll diff --git a/devops/ci-local/.env.local.sample b/devops/ci-local/.env.local.sample new file mode 100644 index 000000000..58833dc8c --- /dev/null +++ b/devops/ci-local/.env.local.sample @@ -0,0 +1,147 @@ +# ============================================================================= +# LOCAL CI TESTING ENVIRONMENT VARIABLES +# ============================================================================= +# Copy this file to .env.local and customize for your local environment. +# The .env.local file is gitignored and should NOT be committed. +# +# Usage: +# cp devops/ci-local/.env.local.sample devops/ci-local/.env.local +# # Edit .env.local with your values +# +# ============================================================================= + +# ============================================================================= +# DATABASE CONFIGURATION +# ============================================================================= +# These values match docker-compose.ci.yaml defaults +# Port 5433 is used to avoid conflicts with development PostgreSQL + +STELLAOPS_TEST_POSTGRES_CONNECTION="Host=localhost;Port=5433;Database=stellaops_test;Username=stellaops_ci;Password=ci_test_password" + +# Alternative connection string format +POSTGRES_HOST=localhost +POSTGRES_PORT=5433 +POSTGRES_USER=stellaops_ci +POSTGRES_PASSWORD=ci_test_password +POSTGRES_DB=stellaops_test + +# ============================================================================= +# CACHE & MESSAGING +# ============================================================================= +# Valkey (Redis-compatible) - Port 6380 to avoid conflicts +VALKEY_CONNECTION_STRING="localhost:6380" +VALKEY_HOST=localhost +VALKEY_PORT=6380 + +# NATS JetStream - Port 4223 to avoid conflicts +#NATS_URL="nats://localhost:4223" +#NATS_HOST=localhost +#NATS_PORT=4223 + +# ============================================================================= +# MOCK CONTAINER REGISTRY +# ============================================================================= +# Local registry for release dry-run testing +REGISTRY_HOST=localhost:5001 +REGISTRY_USERNAME=local +REGISTRY_PASSWORD=local + +# ============================================================================= +# MOCK S3 STORAGE (RustFS) +# ============================================================================= +S3_ENDPOINT=http://localhost:9100 +S3_ACCESS_KEY=rustfsadmin +S3_SECRET_KEY=rustfsadmin +S3_BUCKET=stellaops-ci + +# ============================================================================= +# SIGNING CONFIGURATION +# ============================================================================= +# Mock signing keys for local testing - DO NOT USE IN PRODUCTION! +# Generate real keys with: cosign generate-key-pair + +# Base64-encoded private key (leave empty to skip signing tests) +COSIGN_PRIVATE_KEY_B64= + +# Password for the signing key +COSIGN_PASSWORD=local-test-password + +# For keyless signing (requires internet) +# COSIGN_EXPERIMENTAL=1 + +# ============================================================================= +# OPTIONAL: REAL SECRETS FOR FULL TESTING +# ============================================================================= +# Uncomment and fill in for full integration testing +# These are NOT required for basic local CI runs + +# Gitea API token for registry operations +# GITEA_TOKEN= + +# GitHub Container Registry token +# GHCR_TOKEN= + +# AI API key for AdvisoryAI tests +# AI_API_KEY= + +# Slack webhook for notification tests +# SLACK_WEBHOOK= + +# ============================================================================= +# LOCAL CI CONFIGURATION +# ============================================================================= + +# Execution mode: docker, native, or act +LOCAL_CI_MODE=docker + +# Number of parallel test runners (default: auto-detect CPU count) +LOCAL_CI_PARALLEL=4 + +# Enable verbose output +LOCAL_CI_VERBOSE=false + +# Results output directory (relative to repo root) +LOCAL_CI_RESULTS_DIR=out/local-ci + +# ============================================================================= +# DEPLOYMENT FLAGS +# ============================================================================= +# Always dry-run for local testing +DEPLOYMENT_DRY_RUN=true + +# Mock deployment targets +DEPLOYMENT_HOST=localhost +DEPLOYMENT_USERNAME=testuser +DEPLOYMENT_PATH=/tmp/stellaops-deploy + +# ============================================================================= +# FEATURE FLAGS +# ============================================================================= + +# Skip tests requiring external network access +STELLAOPS_SKIP_NETWORK_TESTS=false + +# Enable offline mode (uses cached/mock data) +STELLAOPS_OFFLINE_MODE=false + +# Skip slow benchmark tests +SKIP_BENCHMARK_TESTS=true + +# Skip chaos/resilience tests +SKIP_CHAOS_TESTS=true + +# ============================================================================= +# .NET BUILD CONFIGURATION +# ============================================================================= +# These match CI environment exactly + +DOTNET_NOLOGO=1 +DOTNET_CLI_TELEMETRY_OPTOUT=1 +DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 +TZ=UTC + +# Build configuration +BUILD_CONFIGURATION=Release + +# Warnings as errors (match CI) +DOTNET_WARNASERROR=true diff --git a/devops/ci-local/events/pull-request.json b/devops/ci-local/events/pull-request.json new file mode 100644 index 000000000..0c788a493 --- /dev/null +++ b/devops/ci-local/events/pull-request.json @@ -0,0 +1,48 @@ +{ + "action": "opened", + "number": 999, + "pull_request": { + "number": 999, + "title": "[Local CI] Test Pull Request", + "body": "This is a simulated pull request for local CI testing.", + "state": "open", + "draft": false, + "head": { + "ref": "feature/local-ci-test", + "sha": "0000000000000000000000000000000000000000", + "repo": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org" + } + }, + "base": { + "ref": "main", + "sha": "0000000000000000000000000000000000000001", + "repo": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org" + } + }, + "labels": [], + "user": { + "login": "local-ci-user", + "type": "User" + }, + "created_at": "2025-01-01T00:00:00Z", + "updated_at": "2025-01-01T00:00:00Z" + }, + "repository": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org", + "default_branch": "main", + "private": true, + "owner": { + "login": "stellaops", + "type": "Organization" + } + }, + "sender": { + "login": "local-ci-user", + "type": "User" + } +} diff --git a/devops/ci-local/events/push-main.json b/devops/ci-local/events/push-main.json new file mode 100644 index 000000000..eb184ef3e --- /dev/null +++ b/devops/ci-local/events/push-main.json @@ -0,0 +1,54 @@ +{ + "ref": "refs/heads/main", + "before": "0000000000000000000000000000000000000001", + "after": "0000000000000000000000000000000000000002", + "created": false, + "deleted": false, + "forced": false, + "compare": "https://git.stella-ops.org/compare/000001...000002", + "commits": [ + { + "id": "0000000000000000000000000000000000000002", + "message": "[Local CI] Test commit on main branch", + "timestamp": "2025-01-01T00:00:00Z", + "author": { + "name": "Local CI User", + "email": "local-ci@stella-ops.org" + }, + "committer": { + "name": "Local CI User", + "email": "local-ci@stella-ops.org" + }, + "added": [], + "removed": [], + "modified": ["src/Scanner/StellaOps.Scanner.Core/Scanner.cs"] + } + ], + "head_commit": { + "id": "0000000000000000000000000000000000000002", + "message": "[Local CI] Test commit on main branch", + "timestamp": "2025-01-01T00:00:00Z", + "author": { + "name": "Local CI User", + "email": "local-ci@stella-ops.org" + } + }, + "repository": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org", + "default_branch": "main", + "private": true, + "owner": { + "login": "stellaops", + "type": "Organization" + } + }, + "pusher": { + "name": "local-ci-user", + "email": "local-ci@stella-ops.org" + }, + "sender": { + "login": "local-ci-user", + "type": "User" + } +} diff --git a/devops/ci-local/events/release-tag.json b/devops/ci-local/events/release-tag.json new file mode 100644 index 000000000..20699d65f --- /dev/null +++ b/devops/ci-local/events/release-tag.json @@ -0,0 +1,21 @@ +{ + "ref": "refs/tags/suite-2026.04", + "ref_type": "tag", + "master_branch": "main", + "description": "StellaOps Suite Release 2026.04", + "pusher_type": "user", + "repository": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org", + "default_branch": "main", + "private": true, + "owner": { + "login": "stellaops", + "type": "Organization" + } + }, + "sender": { + "login": "release-manager", + "type": "User" + } +} diff --git a/devops/ci-local/events/schedule.json b/devops/ci-local/events/schedule.json new file mode 100644 index 000000000..7c1f4eb7c --- /dev/null +++ b/devops/ci-local/events/schedule.json @@ -0,0 +1,22 @@ +{ + "schedule": [ + { + "cron": "0 5 * * *" + } + ], + "repository": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org", + "default_branch": "main", + "private": true, + "owner": { + "login": "stellaops", + "type": "Organization" + } + }, + "sender": { + "login": "github-actions[bot]", + "type": "Bot" + }, + "workflow": ".gitea/workflows/nightly-regression.yml" +} diff --git a/devops/ci-local/events/workflow-dispatch.json b/devops/ci-local/events/workflow-dispatch.json new file mode 100644 index 000000000..cb8d90e13 --- /dev/null +++ b/devops/ci-local/events/workflow-dispatch.json @@ -0,0 +1,31 @@ +{ + "action": "workflow_dispatch", + "inputs": { + "dry_run": "true", + "include_performance": "false", + "include_benchmark": "false", + "include_airgap": "false", + "include_chaos": "false", + "include_determinism": "false", + "include_resilience": "false", + "include_observability": "false", + "force_deploy": "false", + "environment": "local" + }, + "ref": "refs/heads/main", + "repository": { + "name": "git.stella-ops.org", + "full_name": "stellaops/git.stella-ops.org", + "default_branch": "main", + "private": true, + "owner": { + "login": "stellaops", + "type": "Organization" + } + }, + "sender": { + "login": "local-ci-user", + "type": "User" + }, + "workflow": ".gitea/workflows/test-matrix.yml" +} diff --git a/devops/compose/docker-compose.ci.yaml b/devops/compose/docker-compose.ci.yaml new file mode 100644 index 000000000..48b2e14bc --- /dev/null +++ b/devops/compose/docker-compose.ci.yaml @@ -0,0 +1,130 @@ +# ============================================================================= +# LOCAL CI TESTING SERVICES +# ============================================================================= +# Docker Compose profile for running CI tests locally. +# Uses different ports to avoid conflicts with development services. +# +# Usage: +# docker compose -f devops/compose/docker-compose.ci.yaml up -d +# docker compose -f devops/compose/docker-compose.ci.yaml down -v +# +# Services: +# - postgres-ci: PostgreSQL 16 for integration tests (port 5433) +# - valkey-ci: Valkey/Redis for caching tests (port 6380) +# - nats-ci: NATS JetStream for messaging tests (port 4223) +# - mock-registry: Local container registry for release testing (port 5001) +# +# ============================================================================= + +networks: + ci-net: + driver: bridge + name: stellaops-ci-net + +volumes: + ci-postgres-data: + name: stellaops-ci-postgres + ci-valkey-data: + name: stellaops-ci-valkey + +services: + # --------------------------------------------------------------------------- + # PostgreSQL 16 - Primary database for integration tests + # --------------------------------------------------------------------------- + postgres-ci: + image: postgres:16-alpine + container_name: stellaops-postgres-ci + environment: + POSTGRES_USER: stellaops_ci + POSTGRES_PASSWORD: ci_test_password + POSTGRES_DB: stellaops_test + # Performance tuning for tests + POSTGRES_INITDB_ARGS: "--data-checksums" + ports: + - "5433:5432" # Different port to avoid conflicts with dev + volumes: + - ci-postgres-data:/var/lib/postgresql/data + networks: + - ci-net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U stellaops_ci -d stellaops_test"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Valkey 8.0 - Redis-compatible cache for caching tests + # --------------------------------------------------------------------------- + valkey-ci: + image: valkey/valkey:8.0-alpine + container_name: stellaops-valkey-ci + command: ["valkey-server", "--appendonly", "yes", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"] + ports: + - "6380:6379" # Different port to avoid conflicts + volumes: + - ci-valkey-data:/data + networks: + - ci-net + healthcheck: + test: ["CMD", "valkey-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + + # --------------------------------------------------------------------------- + # NATS JetStream - Message queue for messaging tests + # --------------------------------------------------------------------------- + nats-ci: + image: nats:2.10-alpine + container_name: stellaops-nats-ci + command: ["-js", "-sd", "/data", "-m", "8222"] + ports: + - "4223:4222" # Client port (different from dev) + - "8223:8222" # Monitoring port + networks: + - ci-net + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:8222/healthz"] + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Mock Container Registry - For release dry-run testing + # --------------------------------------------------------------------------- + mock-registry: + image: registry:2 + container_name: stellaops-registry-ci + ports: + - "5001:5000" + environment: + REGISTRY_STORAGE_DELETE_ENABLED: "true" + networks: + - ci-net + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Mock S3 (MinIO) - For artifact storage tests + # --------------------------------------------------------------------------- + minio-ci: + image: minio/minio:latest + container_name: stellaops-minio-ci + command: server /data --console-address ":9001" + ports: + - "9100:9000" # S3 API port + - "9101:9001" # Console port + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + networks: + - ci-net + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped diff --git a/devops/compose/docker-compose.dev.yaml b/devops/compose/docker-compose.dev.yaml index 2e55de8e0..5e66f5b8d 100644 --- a/devops/compose/docker-compose.dev.yaml +++ b/devops/compose/docker-compose.dev.yaml @@ -28,6 +28,7 @@ services: PGDATA: /var/lib/postgresql/data/pgdata volumes: - postgres-data:/var/lib/postgresql/data + - ./postgres-init:/docker-entrypoint-initdb.d:ro ports: - "${POSTGRES_PORT:-5432}:5432" networks: diff --git a/devops/compose/postgres-init/01-extensions.sql b/devops/compose/postgres-init/01-extensions.sql index 463e981d9..6de17d48a 100644 --- a/devops/compose/postgres-init/01-extensions.sql +++ b/devops/compose/postgres-init/01-extensions.sql @@ -1,5 +1,7 @@ --- PostgreSQL initialization for StellaOps air-gap deployment +-- ============================================================================ +-- PostgreSQL initialization for StellaOps -- This script runs automatically on first container start +-- ============================================================================ -- Enable pg_stat_statements extension for query performance analysis CREATE EXTENSION IF NOT EXISTS pg_stat_statements; @@ -9,25 +11,59 @@ CREATE EXTENSION IF NOT EXISTS pg_trgm; -- Fuzzy text search CREATE EXTENSION IF NOT EXISTS btree_gin; -- GIN indexes for scalar types CREATE EXTENSION IF NOT EXISTS pgcrypto; -- Cryptographic functions +-- ============================================================================ -- Create schemas for all modules -- Migrations will create tables within these schemas -CREATE SCHEMA IF NOT EXISTS authority; -CREATE SCHEMA IF NOT EXISTS vuln; -CREATE SCHEMA IF NOT EXISTS vex; -CREATE SCHEMA IF NOT EXISTS scheduler; -CREATE SCHEMA IF NOT EXISTS notify; -CREATE SCHEMA IF NOT EXISTS policy; -CREATE SCHEMA IF NOT EXISTS concelier; -CREATE SCHEMA IF NOT EXISTS audit; -CREATE SCHEMA IF NOT EXISTS unknowns; +-- ============================================================================ --- Grant usage to application user (assumes POSTGRES_USER is the app user) -GRANT USAGE ON SCHEMA authority TO PUBLIC; -GRANT USAGE ON SCHEMA vuln TO PUBLIC; -GRANT USAGE ON SCHEMA vex TO PUBLIC; -GRANT USAGE ON SCHEMA scheduler TO PUBLIC; -GRANT USAGE ON SCHEMA notify TO PUBLIC; -GRANT USAGE ON SCHEMA policy TO PUBLIC; -GRANT USAGE ON SCHEMA concelier TO PUBLIC; -GRANT USAGE ON SCHEMA audit TO PUBLIC; -GRANT USAGE ON SCHEMA unknowns TO PUBLIC; +-- Core Platform +CREATE SCHEMA IF NOT EXISTS authority; -- Authentication, authorization, OAuth/OIDC + +-- Data Ingestion +CREATE SCHEMA IF NOT EXISTS vuln; -- Concelier vulnerability data +CREATE SCHEMA IF NOT EXISTS vex; -- Excititor VEX documents + +-- Scanning & Analysis +CREATE SCHEMA IF NOT EXISTS scanner; -- Container scanning, SBOM generation + +-- Scheduling & Orchestration +CREATE SCHEMA IF NOT EXISTS scheduler; -- Job scheduling +CREATE SCHEMA IF NOT EXISTS taskrunner; -- Task execution + +-- Policy & Risk +CREATE SCHEMA IF NOT EXISTS policy; -- Policy engine +CREATE SCHEMA IF NOT EXISTS unknowns; -- Unknown component tracking + +-- Artifacts & Evidence +CREATE SCHEMA IF NOT EXISTS proofchain; -- Attestor proof chains +CREATE SCHEMA IF NOT EXISTS attestor; -- Attestor submission queue +CREATE SCHEMA IF NOT EXISTS signer; -- Key management + +-- Notifications +CREATE SCHEMA IF NOT EXISTS notify; -- Notification delivery + +-- Signals & Observability +CREATE SCHEMA IF NOT EXISTS signals; -- Runtime signals + +-- Registry +CREATE SCHEMA IF NOT EXISTS packs; -- Task packs registry + +-- Audit +CREATE SCHEMA IF NOT EXISTS audit; -- System-wide audit log + +-- ============================================================================ +-- Grant usage to application user (for single-user mode) +-- Per-module users are created in 02-create-users.sql +-- ============================================================================ +DO $$ +DECLARE + schema_name TEXT; +BEGIN + FOR schema_name IN SELECT unnest(ARRAY[ + 'authority', 'vuln', 'vex', 'scanner', 'scheduler', 'taskrunner', + 'policy', 'unknowns', 'proofchain', 'attestor', 'signer', + 'notify', 'signals', 'packs', 'audit' + ]) LOOP + EXECUTE format('GRANT USAGE ON SCHEMA %I TO PUBLIC', schema_name); + END LOOP; +END $$; diff --git a/devops/compose/postgres-init/02-create-users.sql b/devops/compose/postgres-init/02-create-users.sql new file mode 100644 index 000000000..9f3f02da5 --- /dev/null +++ b/devops/compose/postgres-init/02-create-users.sql @@ -0,0 +1,53 @@ +-- ============================================================================ +-- Per-Module Database Users +-- ============================================================================ +-- Creates isolated database users for each StellaOps module. +-- This enables least-privilege access control and audit trail per module. +-- +-- Password format: {module}_dev (for development only) +-- In production, use secrets management and rotate credentials. +-- ============================================================================ + +-- Core Platform +CREATE USER authority_user WITH PASSWORD 'authority_dev'; + +-- Data Ingestion +CREATE USER concelier_user WITH PASSWORD 'concelier_dev'; +CREATE USER excititor_user WITH PASSWORD 'excititor_dev'; + +-- Scanning & Analysis +CREATE USER scanner_user WITH PASSWORD 'scanner_dev'; + +-- Scheduling & Orchestration +CREATE USER scheduler_user WITH PASSWORD 'scheduler_dev'; +CREATE USER taskrunner_user WITH PASSWORD 'taskrunner_dev'; + +-- Policy & Risk +CREATE USER policy_user WITH PASSWORD 'policy_dev'; +CREATE USER unknowns_user WITH PASSWORD 'unknowns_dev'; + +-- Artifacts & Evidence +CREATE USER attestor_user WITH PASSWORD 'attestor_dev'; +CREATE USER signer_user WITH PASSWORD 'signer_dev'; + +-- Notifications +CREATE USER notify_user WITH PASSWORD 'notify_dev'; + +-- Signals & Observability +CREATE USER signals_user WITH PASSWORD 'signals_dev'; + +-- Registry +CREATE USER packs_user WITH PASSWORD 'packs_dev'; + +-- ============================================================================ +-- Log created users +-- ============================================================================ +DO $$ +BEGIN + RAISE NOTICE 'Created per-module database users:'; + RAISE NOTICE ' - authority_user, concelier_user, excititor_user'; + RAISE NOTICE ' - scanner_user, scheduler_user, taskrunner_user'; + RAISE NOTICE ' - policy_user, unknowns_user'; + RAISE NOTICE ' - attestor_user, signer_user'; + RAISE NOTICE ' - notify_user, signals_user, packs_user'; +END $$; diff --git a/devops/compose/postgres-init/03-grant-permissions.sql b/devops/compose/postgres-init/03-grant-permissions.sql new file mode 100644 index 000000000..a66092b4c --- /dev/null +++ b/devops/compose/postgres-init/03-grant-permissions.sql @@ -0,0 +1,153 @@ +-- ============================================================================ +-- Per-Module Schema Permissions +-- ============================================================================ +-- Grants each module user access to their respective schema(s). +-- Users can only access tables in their designated schemas. +-- ============================================================================ + +-- ============================================================================ +-- Authority Module +-- ============================================================================ +GRANT USAGE ON SCHEMA authority TO authority_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA authority TO authority_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA authority TO authority_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA authority GRANT ALL ON TABLES TO authority_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA authority GRANT ALL ON SEQUENCES TO authority_user; + +-- ============================================================================ +-- Concelier Module (uses 'vuln' schema) +-- ============================================================================ +GRANT USAGE ON SCHEMA vuln TO concelier_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA vuln TO concelier_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA vuln TO concelier_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA vuln GRANT ALL ON TABLES TO concelier_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA vuln GRANT ALL ON SEQUENCES TO concelier_user; + +-- ============================================================================ +-- Excititor Module (uses 'vex' schema) +-- ============================================================================ +GRANT USAGE ON SCHEMA vex TO excititor_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA vex TO excititor_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA vex TO excititor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA vex GRANT ALL ON TABLES TO excititor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA vex GRANT ALL ON SEQUENCES TO excititor_user; + +-- ============================================================================ +-- Scanner Module +-- ============================================================================ +GRANT USAGE ON SCHEMA scanner TO scanner_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA scanner TO scanner_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA scanner TO scanner_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA scanner GRANT ALL ON TABLES TO scanner_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA scanner GRANT ALL ON SEQUENCES TO scanner_user; + +-- ============================================================================ +-- Scheduler Module +-- ============================================================================ +GRANT USAGE ON SCHEMA scheduler TO scheduler_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA scheduler TO scheduler_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA scheduler TO scheduler_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA scheduler GRANT ALL ON TABLES TO scheduler_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA scheduler GRANT ALL ON SEQUENCES TO scheduler_user; + +-- ============================================================================ +-- TaskRunner Module +-- ============================================================================ +GRANT USAGE ON SCHEMA taskrunner TO taskrunner_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA taskrunner TO taskrunner_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA taskrunner TO taskrunner_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA taskrunner GRANT ALL ON TABLES TO taskrunner_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA taskrunner GRANT ALL ON SEQUENCES TO taskrunner_user; + +-- ============================================================================ +-- Policy Module +-- ============================================================================ +GRANT USAGE ON SCHEMA policy TO policy_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA policy TO policy_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA policy TO policy_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA policy GRANT ALL ON TABLES TO policy_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA policy GRANT ALL ON SEQUENCES TO policy_user; + +-- ============================================================================ +-- Unknowns Module +-- ============================================================================ +GRANT USAGE ON SCHEMA unknowns TO unknowns_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA unknowns TO unknowns_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA unknowns TO unknowns_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA unknowns GRANT ALL ON TABLES TO unknowns_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA unknowns GRANT ALL ON SEQUENCES TO unknowns_user; + +-- ============================================================================ +-- Attestor Module (uses 'proofchain' and 'attestor' schemas) +-- ============================================================================ +GRANT USAGE ON SCHEMA proofchain TO attestor_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA proofchain TO attestor_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA proofchain TO attestor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA proofchain GRANT ALL ON TABLES TO attestor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA proofchain GRANT ALL ON SEQUENCES TO attestor_user; + +GRANT USAGE ON SCHEMA attestor TO attestor_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA attestor TO attestor_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA attestor TO attestor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA attestor GRANT ALL ON TABLES TO attestor_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA attestor GRANT ALL ON SEQUENCES TO attestor_user; + +-- ============================================================================ +-- Signer Module +-- ============================================================================ +GRANT USAGE ON SCHEMA signer TO signer_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA signer TO signer_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA signer TO signer_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA signer GRANT ALL ON TABLES TO signer_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA signer GRANT ALL ON SEQUENCES TO signer_user; + +-- ============================================================================ +-- Notify Module +-- ============================================================================ +GRANT USAGE ON SCHEMA notify TO notify_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA notify TO notify_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA notify TO notify_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA notify GRANT ALL ON TABLES TO notify_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA notify GRANT ALL ON SEQUENCES TO notify_user; + +-- ============================================================================ +-- Signals Module +-- ============================================================================ +GRANT USAGE ON SCHEMA signals TO signals_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA signals TO signals_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA signals TO signals_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA signals GRANT ALL ON TABLES TO signals_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA signals GRANT ALL ON SEQUENCES TO signals_user; + +-- ============================================================================ +-- Packs Registry Module +-- ============================================================================ +GRANT USAGE ON SCHEMA packs TO packs_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA packs TO packs_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA packs TO packs_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA packs GRANT ALL ON TABLES TO packs_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA packs GRANT ALL ON SEQUENCES TO packs_user; + +-- ============================================================================ +-- Verification +-- ============================================================================ +DO $$ +DECLARE + v_user TEXT; + v_schema TEXT; +BEGIN + RAISE NOTICE 'Per-module permissions granted:'; + RAISE NOTICE ' authority_user -> authority'; + RAISE NOTICE ' concelier_user -> vuln'; + RAISE NOTICE ' excititor_user -> vex'; + RAISE NOTICE ' scanner_user -> scanner'; + RAISE NOTICE ' scheduler_user -> scheduler'; + RAISE NOTICE ' taskrunner_user -> taskrunner'; + RAISE NOTICE ' policy_user -> policy'; + RAISE NOTICE ' unknowns_user -> unknowns'; + RAISE NOTICE ' attestor_user -> proofchain, attestor'; + RAISE NOTICE ' signer_user -> signer'; + RAISE NOTICE ' notify_user -> notify'; + RAISE NOTICE ' signals_user -> signals'; + RAISE NOTICE ' packs_user -> packs'; +END $$; diff --git a/devops/docker/repro-builders/BUILD_ENVIRONMENT.md b/devops/docker/repro-builders/BUILD_ENVIRONMENT.md new file mode 100644 index 000000000..b28ba5157 --- /dev/null +++ b/devops/docker/repro-builders/BUILD_ENVIRONMENT.md @@ -0,0 +1,318 @@ +# Reproducible Build Environment Requirements + +**Sprint:** SPRINT_1227_0002_0001_LB_reproducible_builders +**Task:** T12 — Document build environment requirements + +--- + +## Overview + +This document describes the environment requirements for running reproducible distro package builds. The build system supports Alpine, Debian, and RHEL package ecosystems. + +--- + +## Hardware Requirements + +### Minimum Requirements + +| Resource | Minimum | Recommended | +|----------|---------|-------------| +| CPU | 4 cores | 8+ cores | +| RAM | 8 GB | 16+ GB | +| Disk | 50 GB SSD | 200+ GB NVMe | +| Network | 10 Mbps | 100+ Mbps | + +### Storage Breakdown + +| Directory | Purpose | Estimated Size | +|-----------|---------|----------------| +| `/var/lib/docker` | Docker images and containers | 30 GB | +| `/var/cache/stellaops/builds` | Build cache | 50 GB | +| `/var/cache/stellaops/sources` | Source package cache | 20 GB | +| `/var/cache/stellaops/artifacts` | Output artifacts | 50 GB | + +--- + +## Software Requirements + +### Host System + +| Component | Version | Purpose | +|-----------|---------|---------| +| Docker | 24.0+ | Container runtime | +| Docker Compose | 2.20+ | Multi-container orchestration | +| .NET SDK | 10.0 | Worker service runtime | +| objdump | binutils 2.40+ | Binary analysis | +| readelf | binutils 2.40+ | ELF parsing | + +### Container Images + +The build system uses the following base images: + +| Builder | Base Image | Tag | +|---------|------------|-----| +| Alpine | `alpine` | `3.19`, `3.18` | +| Debian | `debian` | `bookworm`, `bullseye` | +| RHEL | `almalinux` | `9`, `8` | + +--- + +## Environment Variables + +### Required Variables + +```bash +# Build configuration +export STELLAOPS_BUILD_CACHE=/var/cache/stellaops/builds +export STELLAOPS_SOURCE_CACHE=/var/cache/stellaops/sources +export STELLAOPS_ARTIFACT_DIR=/var/cache/stellaops/artifacts + +# Reproducibility settings +export TZ=UTC +export LC_ALL=C.UTF-8 +export SOURCE_DATE_EPOCH=$(date +%s) + +# Docker settings +export DOCKER_BUILDKIT=1 +export COMPOSE_DOCKER_CLI_BUILD=1 +``` + +### Optional Variables + +```bash +# Parallel build settings +export STELLAOPS_MAX_CONCURRENT_BUILDS=2 +export STELLAOPS_BUILD_TIMEOUT=1800 # 30 minutes + +# Proxy settings (if behind corporate firewall) +export HTTP_PROXY=http://proxy:8080 +export HTTPS_PROXY=http://proxy:8080 +export NO_PROXY=localhost,127.0.0.1 +``` + +--- + +## Builder-Specific Requirements + +### Alpine Builder + +```dockerfile +# Required packages in builder image +apk add --no-cache \ + alpine-sdk \ + abuild \ + sudo \ + binutils \ + elfutils \ + build-base +``` + +**Normalization requirements:** +- `SOURCE_DATE_EPOCH` must be set +- Use `abuild -r` with reproducible flags +- Archive ordering: `--sort=name` + +### Debian Builder + +```dockerfile +# Required packages in builder image +apt-get install -y \ + build-essential \ + devscripts \ + dpkg-dev \ + fakeroot \ + binutils \ + elfutils \ + debhelper +``` + +**Normalization requirements:** +- Use `dpkg-buildpackage -b` with reproducible flags +- Set `DEB_BUILD_OPTIONS=reproducible` +- Apply `dh_strip_nondeterminism` post-build + +### RHEL Builder + +```dockerfile +# Required packages in builder image (AlmaLinux 9) +dnf install -y \ + mock \ + rpm-build \ + rpmdevtools \ + binutils \ + elfutils +``` + +**Normalization requirements:** +- Use mock with `--enable-network=false` +- Configure mock for deterministic builds +- Set `%_buildhost stellaops.build` + +--- + +## Compiler Flags for Reproducibility + +### C/C++ Flags + +```bash +CFLAGS="-fno-record-gcc-switches -fdebug-prefix-map=$(pwd)=/build -grecord-gcc-switches=off" +CXXFLAGS="${CFLAGS}" +LDFLAGS="-Wl,--build-id=sha1" +``` + +### Additional Flags + +```bash +# Disable date/time macros +-Wdate-time -Werror=date-time + +# Normalize paths +-fmacro-prefix-map=$(pwd)=/build +-ffile-prefix-map=$(pwd)=/build +``` + +--- + +## Archive Determinism + +### ar (Static Libraries) + +```bash +# Use deterministic mode +ar --enable-deterministic-archives crs libfoo.a *.o + +# Or set environment variable +export AR_FLAGS=--enable-deterministic-archives +``` + +### tar (Package Archives) + +```bash +# Deterministic tar creation +tar --sort=name \ + --mtime="@${SOURCE_DATE_EPOCH}" \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + -cf archive.tar directory/ +``` + +### zip/gzip + +```bash +# Use gzip -n to avoid timestamp +gzip -n file + +# Use mtime for consistent timestamps +touch -d "@${SOURCE_DATE_EPOCH}" file +``` + +--- + +## Network Requirements + +### Outbound Access Required + +| Destination | Port | Purpose | +|-------------|------|---------| +| `dl-cdn.alpinelinux.org` | 443 | Alpine packages | +| `deb.debian.org` | 443 | Debian packages | +| `vault.centos.org` | 443 | CentOS/RHEL sources | +| `mirror.almalinux.org` | 443 | AlmaLinux packages | +| `git.*.org` | 443 | Upstream source repos | + +### Air-Gapped Operation + +For air-gapped environments: + +1. Pre-download source packages +2. Configure local mirrors +3. Set `STELLAOPS_OFFLINE_MODE=true` +4. Use cached build artifacts + +--- + +## Security Considerations + +### Container Isolation + +- Builders run in unprivileged containers +- No host network access +- Read-only source mounts +- Ephemeral containers (destroyed after build) + +### Signing Keys + +- Build outputs are unsigned by default +- DSSE signing requires configured key material +- Keys stored in `/etc/stellaops/keys/` or HSM + +### Build Verification + +```bash +# Verify reproducibility +sha256sum build1/output/* > checksums1.txt +sha256sum build2/output/* > checksums2.txt +diff checksums1.txt checksums2.txt +``` + +--- + +## Troubleshooting + +### Common Issues + +| Issue | Cause | Resolution | +|-------|-------|------------| +| Build timestamp differs | `SOURCE_DATE_EPOCH` not set | Export variable before build | +| Path in debug info | Missing `-fdebug-prefix-map` | Add to CFLAGS | +| ar archive differs | Deterministic mode disabled | Use `--enable-deterministic-archives` | +| tar ordering differs | Random file order | Use `--sort=name` | + +### Debugging Reproducibility + +```bash +# Compare two builds byte-by-byte +diffoscope build1/output/libfoo.so build2/output/libfoo.so + +# Check for timestamp differences +objdump -t binary | grep -i time + +# Verify no random UUIDs +strings binary | grep -E '[0-9a-f]{8}-[0-9a-f]{4}' +``` + +--- + +## Monitoring and Metrics + +### Key Metrics + +| Metric | Description | Target | +|--------|-------------|--------| +| `build_reproducibility_rate` | % of reproducible builds | > 95% | +| `build_duration_seconds` | Time to complete build | < 1800 | +| `fingerprint_extraction_rate` | Functions per second | > 1000 | +| `build_cache_hit_rate` | Cache effectiveness | > 80% | + +### Health Checks + +```bash +# Verify builder containers are ready +docker ps --filter "name=repro-builder" + +# Check cache disk usage +df -h /var/cache/stellaops/ + +# Verify build queue +curl -s http://localhost:9090/metrics | grep stellaops_build +``` + +--- + +## References + +- [Reproducible Builds](https://reproducible-builds.org/) +- [Debian Reproducible Builds](https://wiki.debian.org/ReproducibleBuilds) +- [Alpine Reproducibility](https://wiki.alpinelinux.org/wiki/Reproducible_Builds) +- [RPM Reproducibility](https://rpm-software-management.github.io/rpm/manual/reproducibility.html) diff --git a/devops/docker/repro-builders/alpine/Dockerfile b/devops/docker/repro-builders/alpine/Dockerfile new file mode 100644 index 000000000..929e8efdc --- /dev/null +++ b/devops/docker/repro-builders/alpine/Dockerfile @@ -0,0 +1,62 @@ +# Alpine Reproducible Builder +# Creates deterministic builds of Alpine packages for fingerprint diffing +# +# Usage: +# docker build -t repro-builder-alpine:3.20 --build-arg RELEASE=3.20 . +# docker run -v ./output:/output repro-builder-alpine:3.20 build openssl 3.0.7-r0 + +ARG RELEASE=3.20 +FROM alpine:${RELEASE} + +ARG RELEASE +ENV ALPINE_RELEASE=${RELEASE} + +# Install build tools and dependencies +RUN apk add --no-cache \ + alpine-sdk \ + abuild \ + sudo \ + git \ + curl \ + binutils \ + elfutils \ + coreutils \ + tar \ + gzip \ + xz \ + patch \ + diffutils \ + file \ + && rm -rf /var/cache/apk/* + +# Create build user (abuild requires non-root) +RUN adduser -D -G abuild builder \ + && echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers \ + && mkdir -p /var/cache/distfiles \ + && chown -R builder:abuild /var/cache/distfiles + +# Setup abuild +USER builder +WORKDIR /home/builder + +# Generate abuild keys +RUN abuild-keygen -a -i -n + +# Copy normalization and build scripts +COPY --chown=builder:abuild scripts/normalize.sh /usr/local/bin/normalize.sh +COPY --chown=builder:abuild scripts/build.sh /usr/local/bin/build.sh +COPY --chown=builder:abuild scripts/extract-functions.sh /usr/local/bin/extract-functions.sh + +RUN chmod +x /usr/local/bin/*.sh + +# Environment for reproducibility +ENV TZ=UTC +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +# Build output directory +VOLUME /output +WORKDIR /build + +ENTRYPOINT ["/usr/local/bin/build.sh"] +CMD ["--help"] diff --git a/devops/docker/repro-builders/alpine/scripts/build.sh b/devops/docker/repro-builders/alpine/scripts/build.sh new file mode 100644 index 000000000..51ed398b3 --- /dev/null +++ b/devops/docker/repro-builders/alpine/scripts/build.sh @@ -0,0 +1,226 @@ +#!/bin/sh +# Alpine Reproducible Build Script +# Builds packages with deterministic settings for fingerprint generation +# +# Usage: build.sh [build|diff] [patch_url...] +# +# Examples: +# build.sh build openssl 3.0.7-r0 +# build.sh diff openssl 3.0.7-r0 3.0.8-r0 +# build.sh build openssl 3.0.7-r0 https://patch.url/CVE-2023-1234.patch + +set -eu + +COMMAND="${1:-help}" +PACKAGE="${2:-}" +VERSION="${3:-}" +OUTPUT_DIR="${OUTPUT_DIR:-/output}" + +log() { + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >&2 +} + +show_help() { + cat < [patch_urls...] + Build a package with reproducible settings + + build.sh diff + Build two versions and compute fingerprint diff + + build.sh --help + Show this help message + +Environment: + SOURCE_DATE_EPOCH Override timestamp (extracted from APKBUILD if not set) + OUTPUT_DIR Output directory (default: /output) + CFLAGS Additional compiler flags + LDFLAGS Additional linker flags + +Examples: + build.sh build openssl 3.0.7-r0 + build.sh build curl 8.1.0-r0 https://patch/CVE-2023-1234.patch + build.sh diff openssl 3.0.7-r0 3.0.8-r0 +EOF +} + +setup_reproducible_env() { + local pkg="$1" + local ver="$2" + + # Extract SOURCE_DATE_EPOCH from APKBUILD if not set + if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + if [ -f "aports/main/$pkg/APKBUILD" ]; then + # Use pkgrel date or fallback to current + SOURCE_DATE_EPOCH=$(stat -c %Y "aports/main/$pkg/APKBUILD" 2>/dev/null || date +%s) + else + SOURCE_DATE_EPOCH=$(date +%s) + fi + export SOURCE_DATE_EPOCH + fi + + log "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" + + # Reproducible compiler flags + export CFLAGS="${CFLAGS:-} -fno-record-gcc-switches -fdebug-prefix-map=$(pwd)=/build" + export CXXFLAGS="${CXXFLAGS:-} ${CFLAGS}" + export LDFLAGS="${LDFLAGS:-}" + + # Locale for deterministic sorting + export LC_ALL=C.UTF-8 + export TZ=UTC +} + +fetch_source() { + local pkg="$1" + local ver="$2" + + log "Fetching source for $pkg-$ver" + + # Clone aports if needed + if [ ! -d "aports" ]; then + git clone --depth 1 https://gitlab.alpinelinux.org/alpine/aports.git + fi + + # Find package + local pkg_dir="" + for repo in main community testing; do + if [ -d "aports/$repo/$pkg" ]; then + pkg_dir="aports/$repo/$pkg" + break + fi + done + + if [ -z "$pkg_dir" ]; then + log "ERROR: Package $pkg not found in aports" + return 1 + fi + + # Checkout specific version if needed + cd "$pkg_dir" + abuild fetch + abuild unpack +} + +apply_patches() { + local src_dir="$1" + shift + + for patch_url in "$@"; do + log "Applying patch: $patch_url" + curl -sSL "$patch_url" | patch -d "$src_dir" -p1 + done +} + +build_package() { + local pkg="$1" + local ver="$2" + shift 2 + local patches="$@" + + log "Building $pkg-$ver" + + setup_reproducible_env "$pkg" "$ver" + + cd /build + fetch_source "$pkg" "$ver" + + if [ -n "$patches" ]; then + apply_patches "src/$pkg-*" $patches + fi + + # Build with reproducible settings + abuild -r + + # Copy output + local out_dir="$OUTPUT_DIR/$pkg-$ver" + mkdir -p "$out_dir" + cp -r ~/packages/*/*.apk "$out_dir/" 2>/dev/null || true + + # Extract binaries and fingerprints + for apk in "$out_dir"/*.apk; do + [ -f "$apk" ] || continue + local apk_name=$(basename "$apk" .apk) + mkdir -p "$out_dir/extracted/$apk_name" + tar -xzf "$apk" -C "$out_dir/extracted/$apk_name" + + # Extract function fingerprints + /usr/local/bin/extract-functions.sh "$out_dir/extracted/$apk_name" > "$out_dir/$apk_name.functions.json" + done + + log "Build complete: $out_dir" +} + +diff_versions() { + local pkg="$1" + local vuln_ver="$2" + local patched_ver="$3" + + log "Building and diffing $pkg: $vuln_ver vs $patched_ver" + + # Build vulnerable version + build_package "$pkg" "$vuln_ver" + + # Build patched version + build_package "$pkg" "$patched_ver" + + # Compute diff + local diff_out="$OUTPUT_DIR/$pkg-diff-$vuln_ver-vs-$patched_ver.json" + + # Simple diff of function fingerprints + jq -s ' + .[0] as $vuln | + .[1] as $patched | + { + package: "'"$pkg"'", + vulnerable_version: "'"$vuln_ver"'", + patched_version: "'"$patched_ver"'", + vulnerable_functions: ($vuln | length), + patched_functions: ($patched | length), + added: [($patched[] | select(.name as $n | ($vuln | map(.name) | index($n)) == null))], + removed: [($vuln[] | select(.name as $n | ($patched | map(.name) | index($n)) == null))], + modified: [ + $vuln[] | .name as $n | .hash as $h | + ($patched[] | select(.name == $n and .hash != $h)) | + {name: $n, vuln_hash: $h, patched_hash: .hash} + ] + } + ' \ + "$OUTPUT_DIR/$pkg-$vuln_ver"/*.functions.json \ + "$OUTPUT_DIR/$pkg-$patched_ver"/*.functions.json \ + > "$diff_out" + + log "Diff complete: $diff_out" +} + +case "$COMMAND" in + build) + if [ -z "$PACKAGE" ] || [ -z "$VERSION" ]; then + log "ERROR: Package and version required" + show_help + exit 1 + fi + shift 2 # Remove command, package, version + build_package "$PACKAGE" "$VERSION" "$@" + ;; + diff) + PATCHED_VERSION="${4:-}" + if [ -z "$PACKAGE" ] || [ -z "$VERSION" ] || [ -z "$PATCHED_VERSION" ]; then + log "ERROR: Package, vulnerable version, and patched version required" + show_help + exit 1 + fi + diff_versions "$PACKAGE" "$VERSION" "$PATCHED_VERSION" + ;; + --help|help) + show_help + ;; + *) + log "ERROR: Unknown command: $COMMAND" + show_help + exit 1 + ;; +esac diff --git a/devops/docker/repro-builders/alpine/scripts/extract-functions.sh b/devops/docker/repro-builders/alpine/scripts/extract-functions.sh new file mode 100644 index 000000000..e5dd4dc16 --- /dev/null +++ b/devops/docker/repro-builders/alpine/scripts/extract-functions.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# Extract function fingerprints from ELF binaries +# Outputs JSON array with function name, offset, size, and hashes +# +# Usage: extract-functions.sh +# +# Dependencies: objdump, readelf, sha256sum, jq + +set -eu + +DIR="${1:-.}" + +extract_functions_from_binary() { + local binary="$1" + + # Skip non-ELF files + file "$binary" | grep -q "ELF" || return 0 + + # Get function symbols + objdump -t "$binary" 2>/dev/null | \ + awk '/\.text.*[0-9a-f]+.*F/ { + # Fields: addr flags section size name + gsub(/\*.*\*/, "", $1) # Clean address + if ($5 != "" && $4 != "00000000" && $4 != "0000000000000000") { + printf "%s %s %s\n", $1, $4, $NF + } + }' | while read -r offset size name; do + # Skip compiler-generated symbols + case "$name" in + __*|_GLOBAL_*|.plt*|.text*|frame_dummy|register_tm_clones|deregister_tm_clones) + continue + ;; + esac + + # Convert hex size to decimal + dec_size=$((16#$size)) + + # Skip tiny functions (likely padding) + [ "$dec_size" -lt 16 ] && continue + + # Extract function bytes and compute hash + # Using objdump to get disassembly and hash the opcodes + local hash=$(objdump -d --start-address="0x$offset" --stop-address="0x$((16#$offset + dec_size))" "$binary" 2>/dev/null | \ + grep "^[[:space:]]*[0-9a-f]*:" | \ + awk '{for(i=2;i<=NF;i++){if($i~/^[0-9a-f]{2}$/){printf "%s", $i}}}' | \ + sha256sum | cut -d' ' -f1) + + # Output JSON object + printf '{"name":"%s","offset":"0x%s","size":%d,"hash":"%s"}\n' \ + "$name" "$offset" "$dec_size" "${hash:-unknown}" + done +} + +# Find all ELF binaries in directory +echo "[" +first=true +find "$DIR" -type f -executable 2>/dev/null | while read -r binary; do + # Check if ELF + file "$binary" 2>/dev/null | grep -q "ELF" || continue + + extract_functions_from_binary "$binary" | while read -r json; do + [ -z "$json" ] && continue + if [ "$first" = "true" ]; then + first=false + else + echo "," + fi + echo "$json" + done +done +echo "]" diff --git a/devops/docker/repro-builders/alpine/scripts/normalize.sh b/devops/docker/repro-builders/alpine/scripts/normalize.sh new file mode 100644 index 000000000..d35ecd7d8 --- /dev/null +++ b/devops/docker/repro-builders/alpine/scripts/normalize.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Normalization scripts for reproducible builds +# Strips non-deterministic content from build artifacts +# +# Usage: normalize.sh + +set -eu + +DIR="${1:-.}" + +log() { + echo "[normalize] $*" >&2 +} + +# Strip timestamps from __DATE__ and __TIME__ macros +strip_date_time() { + log "Stripping date/time macros..." + # Already handled by SOURCE_DATE_EPOCH in modern GCC +} + +# Normalize build paths +normalize_paths() { + log "Normalizing build paths..." + # Handled by -fdebug-prefix-map +} + +# Normalize ar archives for deterministic ordering +normalize_archives() { + log "Normalizing ar archives..." + find "$DIR" -name "*.a" -type f | while read -r archive; do + if ar --version 2>&1 | grep -q "GNU ar"; then + # GNU ar with deterministic mode + ar -rcsD "$archive.tmp" "$archive" && mv "$archive.tmp" "$archive" 2>/dev/null || true + fi + done +} + +# Strip debug sections that contain non-deterministic info +strip_debug_timestamps() { + log "Stripping debug timestamps..." + find "$DIR" -type f \( -name "*.o" -o -name "*.so" -o -name "*.so.*" -o -executable \) | while read -r obj; do + # Check if ELF + file "$obj" 2>/dev/null | grep -q "ELF" || continue + + # Strip build-id if not needed (we regenerate it) + # objcopy --remove-section=.note.gnu.build-id "$obj" 2>/dev/null || true + + # Remove timestamps from DWARF debug info + # This is typically handled by SOURCE_DATE_EPOCH + done +} + +# Normalize tar archives +normalize_tars() { + log "Normalizing tar archives..." + # When creating tars, use: + # tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner +} + +# Run all normalizations +normalize_paths +normalize_archives +strip_debug_timestamps + +log "Normalization complete" diff --git a/devops/docker/repro-builders/debian/Dockerfile b/devops/docker/repro-builders/debian/Dockerfile new file mode 100644 index 000000000..9d5fafc9b --- /dev/null +++ b/devops/docker/repro-builders/debian/Dockerfile @@ -0,0 +1,59 @@ +# Debian Reproducible Builder +# Creates deterministic builds of Debian packages for fingerprint diffing +# +# Usage: +# docker build -t repro-builder-debian:bookworm --build-arg RELEASE=bookworm . +# docker run -v ./output:/output repro-builder-debian:bookworm build openssl 3.0.7-1 + +ARG RELEASE=bookworm +FROM debian:${RELEASE} + +ARG RELEASE +ENV DEBIAN_RELEASE=${RELEASE} +ENV DEBIAN_FRONTEND=noninteractive + +# Install build tools +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + devscripts \ + dpkg-dev \ + equivs \ + fakeroot \ + git \ + curl \ + ca-certificates \ + binutils \ + elfutils \ + coreutils \ + patch \ + diffutils \ + file \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Create build user +RUN useradd -m -s /bin/bash builder \ + && echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +USER builder +WORKDIR /home/builder + +# Copy scripts +COPY --chown=builder:builder scripts/build.sh /usr/local/bin/build.sh +COPY --chown=builder:builder scripts/extract-functions.sh /usr/local/bin/extract-functions.sh +COPY --chown=builder:builder scripts/normalize.sh /usr/local/bin/normalize.sh + +USER root +RUN chmod +x /usr/local/bin/*.sh +USER builder + +# Environment for reproducibility +ENV TZ=UTC +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +VOLUME /output +WORKDIR /build + +ENTRYPOINT ["/usr/local/bin/build.sh"] +CMD ["--help"] diff --git a/devops/docker/repro-builders/debian/scripts/build.sh b/devops/docker/repro-builders/debian/scripts/build.sh new file mode 100644 index 000000000..fcc72bca0 --- /dev/null +++ b/devops/docker/repro-builders/debian/scripts/build.sh @@ -0,0 +1,233 @@ +#!/bin/bash +# Debian Reproducible Build Script +# Builds packages with deterministic settings for fingerprint generation +# +# Usage: build.sh [build|diff] [patch_url...] + +set -euo pipefail + +COMMAND="${1:-help}" +PACKAGE="${2:-}" +VERSION="${3:-}" +OUTPUT_DIR="${OUTPUT_DIR:-/output}" + +log() { + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >&2 +} + +show_help() { + cat < [patch_urls...] + Build a package with reproducible settings + + build.sh diff + Build two versions and compute fingerprint diff + + build.sh --help + Show this help message + +Environment: + SOURCE_DATE_EPOCH Override timestamp (extracted from changelog if not set) + OUTPUT_DIR Output directory (default: /output) + DEB_BUILD_OPTIONS Additional build options + +Examples: + build.sh build openssl 3.0.7-1 + build.sh diff curl 8.1.0-1 8.1.0-2 +EOF +} + +setup_reproducible_env() { + local pkg="$1" + + # Reproducible build flags + export DEB_BUILD_OPTIONS="${DEB_BUILD_OPTIONS:-} reproducible=+all" + export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}" + + # Compiler flags for reproducibility + export CFLAGS="${CFLAGS:-} -fno-record-gcc-switches -fdebug-prefix-map=$(pwd)=/build" + export CXXFLAGS="${CXXFLAGS:-} ${CFLAGS}" + + export LC_ALL=C.UTF-8 + export TZ=UTC + + log "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" +} + +fetch_source() { + local pkg="$1" + local ver="$2" + + log "Fetching source for $pkg=$ver" + + mkdir -p /build/src + cd /build/src + + # Enable source repositories + sudo sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list.d/*.sources 2>/dev/null || \ + sudo sed -i 's/^# deb-src/deb-src/' /etc/apt/sources.list 2>/dev/null || true + sudo apt-get update + + # Fetch source + if [ -n "$ver" ]; then + apt-get source "${pkg}=${ver}" || apt-get source "$pkg" + else + apt-get source "$pkg" + fi + + # Find extracted directory + local src_dir=$(ls -d "${pkg}"*/ 2>/dev/null | head -1) + if [ -z "$src_dir" ]; then + log "ERROR: Could not find source directory for $pkg" + return 1 + fi + + # Extract SOURCE_DATE_EPOCH from changelog + if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + if [ -f "$src_dir/debian/changelog" ]; then + SOURCE_DATE_EPOCH=$(dpkg-parsechangelog -l "$src_dir/debian/changelog" -S Timestamp 2>/dev/null || date +%s) + export SOURCE_DATE_EPOCH + fi + fi + + echo "$src_dir" +} + +install_build_deps() { + local src_dir="$1" + + log "Installing build dependencies" + cd "$src_dir" + sudo apt-get build-dep -y . || true +} + +apply_patches() { + local src_dir="$1" + shift + + cd "$src_dir" + for patch_url in "$@"; do + log "Applying patch: $patch_url" + curl -sSL "$patch_url" | patch -p1 + done +} + +build_package() { + local pkg="$1" + local ver="$2" + shift 2 + local patches="${@:-}" + + log "Building $pkg version $ver" + + setup_reproducible_env "$pkg" + + cd /build + local src_dir=$(fetch_source "$pkg" "$ver") + + install_build_deps "$src_dir" + + if [ -n "$patches" ]; then + apply_patches "$src_dir" $patches + fi + + cd "$src_dir" + + # Build with reproducible settings + dpkg-buildpackage -b -us -uc + + # Copy output + local out_dir="$OUTPUT_DIR/$pkg-$ver" + mkdir -p "$out_dir" + cp -r /build/src/*.deb "$out_dir/" 2>/dev/null || true + + # Extract and fingerprint + for deb in "$out_dir"/*.deb; do + [ -f "$deb" ] || continue + local deb_name=$(basename "$deb" .deb) + mkdir -p "$out_dir/extracted/$deb_name" + dpkg-deb -x "$deb" "$out_dir/extracted/$deb_name" + + # Extract function fingerprints + /usr/local/bin/extract-functions.sh "$out_dir/extracted/$deb_name" > "$out_dir/$deb_name.functions.json" + done + + log "Build complete: $out_dir" +} + +diff_versions() { + local pkg="$1" + local vuln_ver="$2" + local patched_ver="$3" + + log "Building and diffing $pkg: $vuln_ver vs $patched_ver" + + # Build vulnerable version + build_package "$pkg" "$vuln_ver" + + # Clean build environment + rm -rf /build/src/* + + # Build patched version + build_package "$pkg" "$patched_ver" + + # Compute diff + local diff_out="$OUTPUT_DIR/$pkg-diff-$vuln_ver-vs-$patched_ver.json" + + jq -s ' + .[0] as $vuln | + .[1] as $patched | + { + package: "'"$pkg"'", + vulnerable_version: "'"$vuln_ver"'", + patched_version: "'"$patched_ver"'", + vulnerable_functions: ($vuln | length), + patched_functions: ($patched | length), + added: [($patched[] | select(.name as $n | ($vuln | map(.name) | index($n)) == null))], + removed: [($vuln[] | select(.name as $n | ($patched | map(.name) | index($n)) == null))], + modified: [ + $vuln[] | .name as $n | .hash as $h | + ($patched[] | select(.name == $n and .hash != $h)) | + {name: $n, vuln_hash: $h, patched_hash: .hash} + ] + } + ' \ + "$OUTPUT_DIR/$pkg-$vuln_ver"/*.functions.json \ + "$OUTPUT_DIR/$pkg-$patched_ver"/*.functions.json \ + > "$diff_out" 2>/dev/null || log "Warning: Could not compute diff" + + log "Diff complete: $diff_out" +} + +case "$COMMAND" in + build) + if [ -z "$PACKAGE" ]; then + log "ERROR: Package required" + show_help + exit 1 + fi + shift 2 # Remove command, package + [ -n "${VERSION:-}" ] && shift # Remove version if present + build_package "$PACKAGE" "${VERSION:-}" "$@" + ;; + diff) + PATCHED_VERSION="${4:-}" + if [ -z "$PACKAGE" ] || [ -z "$VERSION" ] || [ -z "$PATCHED_VERSION" ]; then + log "ERROR: Package, vulnerable version, and patched version required" + show_help + exit 1 + fi + diff_versions "$PACKAGE" "$VERSION" "$PATCHED_VERSION" + ;; + --help|help) + show_help + ;; + *) + log "ERROR: Unknown command: $COMMAND" + show_help + exit 1 + ;; +esac diff --git a/devops/docker/repro-builders/debian/scripts/extract-functions.sh b/devops/docker/repro-builders/debian/scripts/extract-functions.sh new file mode 100644 index 000000000..90a1ef80b --- /dev/null +++ b/devops/docker/repro-builders/debian/scripts/extract-functions.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# Extract function fingerprints from ELF binaries +# Outputs JSON array with function name, offset, size, and hashes + +set -euo pipefail + +DIR="${1:-.}" + +extract_functions_from_binary() { + local binary="$1" + + # Skip non-ELF files + file "$binary" 2>/dev/null | grep -q "ELF" || return 0 + + # Get function symbols with objdump + objdump -t "$binary" 2>/dev/null | \ + awk '/\.text.*[0-9a-f]+.*F/ { + gsub(/\*.*\*/, "", $1) + if ($5 != "" && length($4) > 0) { + size = strtonum("0x" $4) + if (size >= 16) { + print $1, $4, $NF + } + } + }' | while read -r offset size name; do + # Skip compiler-generated symbols + case "$name" in + __*|_GLOBAL_*|.plt*|.text*|frame_dummy|register_tm_clones|deregister_tm_clones|_start|_init|_fini) + continue + ;; + esac + + # Convert hex size + dec_size=$((16#$size)) + + # Compute hash of function bytes + local hash=$(objdump -d --start-address="0x$offset" --stop-address="$((16#$offset + dec_size))" "$binary" 2>/dev/null | \ + grep -E "^[[:space:]]*[0-9a-f]+:" | \ + awk '{for(i=2;i<=NF;i++){if($i~/^[0-9a-f]{2}$/){printf "%s", $i}}}' | \ + sha256sum | cut -d' ' -f1) + + [ -n "$hash" ] || hash="unknown" + + printf '{"name":"%s","offset":"0x%s","size":%d,"hash":"%s"}\n' \ + "$name" "$offset" "$dec_size" "$hash" + done +} + +# Output JSON array +echo "[" +first=true + +find "$DIR" -type f \( -executable -o -name "*.so" -o -name "*.so.*" \) 2>/dev/null | while read -r binary; do + file "$binary" 2>/dev/null | grep -q "ELF" || continue + + extract_functions_from_binary "$binary" | while read -r json; do + [ -z "$json" ] && continue + if [ "$first" = "true" ]; then + first=false + echo "$json" + else + echo ",$json" + fi + done +done + +echo "]" diff --git a/devops/docker/repro-builders/debian/scripts/normalize.sh b/devops/docker/repro-builders/debian/scripts/normalize.sh new file mode 100644 index 000000000..971fc47b7 --- /dev/null +++ b/devops/docker/repro-builders/debian/scripts/normalize.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Normalization scripts for Debian reproducible builds + +set -euo pipefail + +DIR="${1:-.}" + +log() { + echo "[normalize] $*" >&2 +} + +normalize_archives() { + log "Normalizing ar archives..." + find "$DIR" -name "*.a" -type f | while read -r archive; do + if ar --version 2>&1 | grep -q "GNU ar"; then + ar -rcsD "$archive.tmp" "$archive" 2>/dev/null && mv "$archive.tmp" "$archive" || true + fi + done +} + +strip_debug_timestamps() { + log "Stripping debug timestamps..." + # Handled by SOURCE_DATE_EPOCH and DEB_BUILD_OPTIONS +} + +normalize_archives +strip_debug_timestamps + +log "Normalization complete" diff --git a/devops/docker/repro-builders/rhel/Dockerfile b/devops/docker/repro-builders/rhel/Dockerfile new file mode 100644 index 000000000..6146aaa40 --- /dev/null +++ b/devops/docker/repro-builders/rhel/Dockerfile @@ -0,0 +1,85 @@ +# RHEL-compatible Reproducible Build Container +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# Task: T3 - RHEL builder with mock-based package building +# +# Uses AlmaLinux 9 as RHEL-compatible base for open source builds. +# Production RHEL builds require valid subscription. + +ARG BASE_IMAGE=almalinux:9 +FROM ${BASE_IMAGE} AS builder + +LABEL org.opencontainers.image.title="StellaOps RHEL Reproducible Builder" +LABEL org.opencontainers.image.description="RHEL-compatible reproducible build environment for security patching" +LABEL org.opencontainers.image.vendor="StellaOps" +LABEL org.opencontainers.image.source="https://github.com/stellaops/stellaops" + +# Install build dependencies +RUN dnf -y update && \ + dnf -y install \ + # Core build tools + rpm-build \ + rpmdevtools \ + rpmlint \ + mock \ + # Compiler toolchain + gcc \ + gcc-c++ \ + make \ + cmake \ + autoconf \ + automake \ + libtool \ + # Package management + dnf-plugins-core \ + yum-utils \ + createrepo_c \ + # Binary analysis + binutils \ + elfutils \ + gdb \ + # Reproducibility + diffoscope \ + # Source control + git \ + patch \ + # Utilities + wget \ + curl \ + jq \ + python3 \ + python3-pip && \ + dnf clean all + +# Create mock user (mock requires non-root) +RUN useradd -m mockbuild && \ + usermod -a -G mock mockbuild + +# Set up rpmbuild directories +RUN mkdir -p /build/{BUILD,RPMS,SOURCES,SPECS,SRPMS} && \ + chown -R mockbuild:mockbuild /build + +# Copy build scripts +COPY scripts/build.sh /usr/local/bin/build.sh +COPY scripts/extract-functions.sh /usr/local/bin/extract-functions.sh +COPY scripts/normalize.sh /usr/local/bin/normalize.sh +COPY scripts/mock-build.sh /usr/local/bin/mock-build.sh + +RUN chmod +x /usr/local/bin/*.sh + +# Set reproducibility environment +ENV TZ=UTC +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +# Deterministic compiler flags +ENV CFLAGS="-fno-record-gcc-switches -fdebug-prefix-map=/build=/buildroot -O2 -g" +ENV CXXFLAGS="${CFLAGS}" + +# Mock configuration for reproducible builds +COPY mock/stellaops-repro.cfg /etc/mock/stellaops-repro.cfg + +WORKDIR /build +USER mockbuild + +ENTRYPOINT ["/usr/local/bin/build.sh"] +CMD ["--help"] diff --git a/devops/docker/repro-builders/rhel/mock/stellaops-repro.cfg b/devops/docker/repro-builders/rhel/mock/stellaops-repro.cfg new file mode 100644 index 000000000..613a97424 --- /dev/null +++ b/devops/docker/repro-builders/rhel/mock/stellaops-repro.cfg @@ -0,0 +1,71 @@ +# StellaOps Reproducible Build Mock Configuration +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# +# Mock configuration optimized for reproducible RHEL/AlmaLinux builds + +config_opts['root'] = 'stellaops-repro' +config_opts['target_arch'] = 'x86_64' +config_opts['legal_host_arches'] = ('x86_64',) +config_opts['chroot_setup_cmd'] = 'install @buildsys-build' +config_opts['dist'] = 'el9' +config_opts['releasever'] = '9' + +# Reproducibility settings +config_opts['use_host_resolv'] = False +config_opts['rpmbuild_networking'] = False +config_opts['cleanup_on_success'] = True +config_opts['cleanup_on_failure'] = True + +# Deterministic build settings +config_opts['macros']['SOURCE_DATE_EPOCH'] = '%{getenv:SOURCE_DATE_EPOCH}' +config_opts['macros']['_buildhost'] = 'stellaops.build' +config_opts['macros']['debug_package'] = '%{nil}' +config_opts['macros']['_default_patch_fuzz'] = '0' + +# Compiler flags for reproducibility +config_opts['macros']['optflags'] = '-O2 -g -fno-record-gcc-switches -fdebug-prefix-map=%{_builddir}=/buildroot' + +# Environment normalization +config_opts['environment']['TZ'] = 'UTC' +config_opts['environment']['LC_ALL'] = 'C.UTF-8' +config_opts['environment']['LANG'] = 'C.UTF-8' + +# Use AlmaLinux as RHEL-compatible base +config_opts['dnf.conf'] = """ +[main] +keepcache=1 +debuglevel=2 +reposdir=/dev/null +logfile=/var/log/yum.log +retries=20 +obsoletes=1 +gpgcheck=0 +assumeyes=1 +syslog_ident=mock +syslog_device= +metadata_expire=0 +mdpolicy=group:primary +best=1 +install_weak_deps=0 +protected_packages= +module_platform_id=platform:el9 +user_agent={{ user_agent }} + +[baseos] +name=AlmaLinux $releasever - BaseOS +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos +enabled=1 +gpgcheck=0 + +[appstream] +name=AlmaLinux $releasever - AppStream +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/appstream +enabled=1 +gpgcheck=0 + +[crb] +name=AlmaLinux $releasever - CRB +mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/crb +enabled=1 +gpgcheck=0 +""" diff --git a/devops/docker/repro-builders/rhel/scripts/build.sh b/devops/docker/repro-builders/rhel/scripts/build.sh new file mode 100644 index 000000000..729b9120e --- /dev/null +++ b/devops/docker/repro-builders/rhel/scripts/build.sh @@ -0,0 +1,213 @@ +#!/bin/bash +# RHEL Reproducible Build Script +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# +# Usage: build.sh --srpm [--patch ] [--output ] + +set -euo pipefail + +# Default values +OUTPUT_DIR="/build/output" +WORK_DIR="/build/work" +SRPM="" +PATCH_FILE="" +SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}" + +usage() { + cat < Path or URL to SRPM file (required) + --patch Path to security patch file (optional) + --output Output directory (default: /build/output) + --epoch SOURCE_DATE_EPOCH value (default: from changelog) + --help Show this help message + +Examples: + $0 --srpm openssl-3.0.7-1.el9.src.rpm --patch CVE-2023-0286.patch + $0 --srpm https://mirror/srpms/curl-8.0.1-1.el9.src.rpm + +EOF + exit 0 +} + +log() { + echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" +} + +error() { + log "ERROR: $*" >&2 + exit 1 +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --srpm) + SRPM="$2" + shift 2 + ;; + --patch) + PATCH_FILE="$2" + shift 2 + ;; + --output) + OUTPUT_DIR="$2" + shift 2 + ;; + --epoch) + SOURCE_DATE_EPOCH="$2" + shift 2 + ;; + --help) + usage + ;; + *) + error "Unknown option: $1" + ;; + esac +done + +[[ -z "${SRPM}" ]] && error "SRPM path required. Use --srpm " + +# Create directories +mkdir -p "${OUTPUT_DIR}" "${WORK_DIR}" +cd "${WORK_DIR}" + +log "Starting RHEL reproducible build" +log "SRPM: ${SRPM}" + +# Download or copy SRPM +if [[ "${SRPM}" =~ ^https?:// ]]; then + log "Downloading SRPM..." + curl -fsSL -o source.src.rpm "${SRPM}" + SRPM="source.src.rpm" +elif [[ ! -f "${SRPM}" ]]; then + error "SRPM file not found: ${SRPM}" +fi + +# Install SRPM +log "Installing SRPM..." +rpm2cpio "${SRPM}" | cpio -idmv + +# Extract SOURCE_DATE_EPOCH from changelog if not provided +if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then + SPEC_FILE=$(find . -name "*.spec" | head -1) + if [[ -n "${SPEC_FILE}" ]]; then + # Extract date from first changelog entry + CHANGELOG_DATE=$(grep -m1 '^\*' "${SPEC_FILE}" | sed 's/^\* //' | cut -d' ' -f1-3) + if [[ -n "${CHANGELOG_DATE}" ]]; then + SOURCE_DATE_EPOCH=$(date -d "${CHANGELOG_DATE}" +%s 2>/dev/null || echo "") + fi + fi + + if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then + SOURCE_DATE_EPOCH=$(date +%s) + log "Warning: Using current time for SOURCE_DATE_EPOCH" + fi +fi + +export SOURCE_DATE_EPOCH +log "SOURCE_DATE_EPOCH: ${SOURCE_DATE_EPOCH}" + +# Apply security patch if provided +if [[ -n "${PATCH_FILE}" ]]; then + if [[ ! -f "${PATCH_FILE}" ]]; then + error "Patch file not found: ${PATCH_FILE}" + fi + + log "Applying security patch: ${PATCH_FILE}" + + # Copy patch to SOURCES + PATCH_NAME=$(basename "${PATCH_FILE}") + cp "${PATCH_FILE}" SOURCES/ + + # Add patch to spec file + SPEC_FILE=$(find . -name "*.spec" | head -1) + if [[ -n "${SPEC_FILE}" ]]; then + # Find last Patch line or Source line + LAST_PATCH=$(grep -n '^Patch[0-9]*:' "${SPEC_FILE}" | tail -1 | cut -d: -f1) + if [[ -z "${LAST_PATCH}" ]]; then + LAST_PATCH=$(grep -n '^Source[0-9]*:' "${SPEC_FILE}" | tail -1 | cut -d: -f1) + fi + + # Calculate next patch number + PATCH_NUM=$(grep -c '^Patch[0-9]*:' "${SPEC_FILE}" || echo 0) + PATCH_NUM=$((PATCH_NUM + 100)) # Use 100+ for security patches + + # Insert patch declaration + sed -i "${LAST_PATCH}a Patch${PATCH_NUM}: ${PATCH_NAME}" "${SPEC_FILE}" + + # Add %patch to %prep if not using autosetup + if ! grep -q '%autosetup' "${SPEC_FILE}"; then + PREP_LINE=$(grep -n '^%prep' "${SPEC_FILE}" | head -1 | cut -d: -f1) + if [[ -n "${PREP_LINE}" ]]; then + # Find last %patch line in %prep + LAST_PATCH_LINE=$(sed -n "${PREP_LINE},\$p" "${SPEC_FILE}" | grep -n '^%patch' | tail -1 | cut -d: -f1) + if [[ -n "${LAST_PATCH_LINE}" ]]; then + INSERT_LINE=$((PREP_LINE + LAST_PATCH_LINE)) + else + INSERT_LINE=$((PREP_LINE + 1)) + fi + sed -i "${INSERT_LINE}a %patch${PATCH_NUM} -p1" "${SPEC_FILE}" + fi + fi + fi +fi + +# Set up rpmbuild tree +log "Setting up rpmbuild tree..." +rpmdev-setuptree || true + +# Copy sources and spec +cp -r SOURCES/* ~/rpmbuild/SOURCES/ 2>/dev/null || true +cp *.spec ~/rpmbuild/SPECS/ 2>/dev/null || true + +# Build using mock for isolation and reproducibility +log "Building with mock (stellaops-repro config)..." +SPEC_FILE=$(find ~/rpmbuild/SPECS -name "*.spec" | head -1) + +if [[ -n "${SPEC_FILE}" ]]; then + # Build SRPM first + rpmbuild -bs "${SPEC_FILE}" + + BUILT_SRPM=$(find ~/rpmbuild/SRPMS -name "*.src.rpm" | head -1) + + if [[ -n "${BUILT_SRPM}" ]]; then + # Build with mock + mock -r stellaops-repro --rebuild "${BUILT_SRPM}" --resultdir="${OUTPUT_DIR}/rpms" + else + error "SRPM build failed" + fi +else + error "No spec file found" +fi + +# Extract function fingerprints from built RPMs +log "Extracting function fingerprints..." +for rpm in "${OUTPUT_DIR}/rpms"/*.rpm; do + if [[ -f "${rpm}" ]] && [[ ! "${rpm}" =~ \.src\.rpm$ ]]; then + /usr/local/bin/extract-functions.sh "${rpm}" "${OUTPUT_DIR}/fingerprints" + fi +done + +# Generate build manifest +log "Generating build manifest..." +cat > "${OUTPUT_DIR}/manifest.json" </dev/null | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/'), + "fingerprint_files": $(find "${OUTPUT_DIR}/fingerprints" -name "*.json" -printf '"%f",' 2>/dev/null | sed 's/,$//' | sed 's/^/[/' | sed 's/$/]/') +} +EOF + +log "Build complete. Output in: ${OUTPUT_DIR}" +log "Manifest: ${OUTPUT_DIR}/manifest.json" diff --git a/devops/docker/repro-builders/rhel/scripts/extract-functions.sh b/devops/docker/repro-builders/rhel/scripts/extract-functions.sh new file mode 100644 index 000000000..dbd64bd24 --- /dev/null +++ b/devops/docker/repro-builders/rhel/scripts/extract-functions.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# RHEL Function Extraction Script +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# +# Extracts function-level fingerprints from RPM packages + +set -euo pipefail + +RPM_PATH="${1:-}" +OUTPUT_DIR="${2:-/build/fingerprints}" + +[[ -z "${RPM_PATH}" ]] && { echo "Usage: $0 [output_dir]"; exit 1; } +[[ ! -f "${RPM_PATH}" ]] && { echo "RPM not found: ${RPM_PATH}"; exit 1; } + +mkdir -p "${OUTPUT_DIR}" + +RPM_NAME=$(rpm -qp --qf '%{NAME}' "${RPM_PATH}" 2>/dev/null) +RPM_VERSION=$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "${RPM_PATH}" 2>/dev/null) + +WORK_DIR=$(mktemp -d) +trap "rm -rf ${WORK_DIR}" EXIT + +cd "${WORK_DIR}" + +# Extract RPM contents +rpm2cpio "${RPM_PATH}" | cpio -idmv 2>/dev/null + +# Find ELF binaries +find . -type f -exec file {} \; | grep -E 'ELF.*(executable|shared object)' | cut -d: -f1 | while read -r binary; do + BINARY_NAME=$(basename "${binary}") + BINARY_PATH="${binary#./}" + + # Get build-id if present + BUILD_ID=$(readelf -n "${binary}" 2>/dev/null | grep 'Build ID:' | awk '{print $3}' || echo "") + + # Extract function symbols + OUTPUT_FILE="${OUTPUT_DIR}/${RPM_NAME}_${BINARY_NAME}.json" + + { + echo "{" + echo " \"package\": \"${RPM_NAME}\"," + echo " \"version\": \"${RPM_VERSION}\"," + echo " \"binary\": \"${BINARY_PATH}\"," + echo " \"build_id\": \"${BUILD_ID}\"," + echo " \"extracted_at\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\"," + echo " \"functions\": [" + + # Extract function addresses and sizes using nm and objdump + FIRST=true + nm -S --defined-only "${binary}" 2>/dev/null | grep -E '^[0-9a-f]+ [0-9a-f]+ [Tt]' | while read -r addr size type name; do + if [[ "${FIRST}" == "true" ]]; then + FIRST=false + else + echo "," + fi + + # Calculate function hash from disassembly + FUNC_HASH=$(objdump -d --start-address=0x${addr} --stop-address=$((0x${addr} + 0x${size})) "${binary}" 2>/dev/null | \ + grep -E '^\s+[0-9a-f]+:' | awk '{$1=""; print}' | sha256sum | cut -d' ' -f1) + + printf ' {"name": "%s", "address": "0x%s", "size": %d, "hash": "%s"}' \ + "${name}" "${addr}" "$((0x${size}))" "${FUNC_HASH}" + done || true + + echo "" + echo " ]" + echo "}" + } > "${OUTPUT_FILE}" + + echo "Extracted: ${OUTPUT_FILE}" +done + +echo "Function extraction complete for: ${RPM_NAME}" diff --git a/devops/docker/repro-builders/rhel/scripts/mock-build.sh b/devops/docker/repro-builders/rhel/scripts/mock-build.sh new file mode 100644 index 000000000..797dab5f8 --- /dev/null +++ b/devops/docker/repro-builders/rhel/scripts/mock-build.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# RHEL Mock Build Script +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# +# Builds SRPMs using mock for isolation and reproducibility + +set -euo pipefail + +SRPM="${1:-}" +RESULT_DIR="${2:-/build/output}" +CONFIG="${3:-stellaops-repro}" + +[[ -z "${SRPM}" ]] && { echo "Usage: $0 [result_dir] [mock_config]"; exit 1; } +[[ ! -f "${SRPM}" ]] && { echo "SRPM not found: ${SRPM}"; exit 1; } + +mkdir -p "${RESULT_DIR}" + +echo "Building SRPM with mock: ${SRPM}" +echo "Config: ${CONFIG}" +echo "Output: ${RESULT_DIR}" + +# Initialize mock if needed +mock -r "${CONFIG}" --init + +# Build with reproducibility settings +mock -r "${CONFIG}" \ + --rebuild "${SRPM}" \ + --resultdir="${RESULT_DIR}" \ + --define "SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH:-$(date +%s)}" \ + --define "_buildhost stellaops.build" \ + --define "debug_package %{nil}" + +echo "Build complete. Results in: ${RESULT_DIR}" +ls -la "${RESULT_DIR}" diff --git a/devops/docker/repro-builders/rhel/scripts/normalize.sh b/devops/docker/repro-builders/rhel/scripts/normalize.sh new file mode 100644 index 000000000..668852855 --- /dev/null +++ b/devops/docker/repro-builders/rhel/scripts/normalize.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# RHEL Build Normalization Script +# Sprint: SPRINT_1227_0002_0001 (Reproducible Builders) +# +# Normalizes RPM build environment for reproducibility + +set -euo pipefail + +# Normalize environment +export TZ=UTC +export LC_ALL=C.UTF-8 +export LANG=C.UTF-8 + +# Deterministic compiler flags +export CFLAGS="${CFLAGS:--fno-record-gcc-switches -fdebug-prefix-map=$(pwd)=/buildroot -O2 -g}" +export CXXFLAGS="${CXXFLAGS:-${CFLAGS}}" + +# Disable debug info that varies +export DEB_BUILD_OPTIONS="nostrip noopt" + +# RPM-specific reproducibility +export RPM_BUILD_NCPUS=1 + +# Normalize timestamps in archives +normalize_ar() { + local archive="$1" + if command -v llvm-ar &>/dev/null; then + llvm-ar --format=gnu --enable-deterministic-archives rcs "${archive}.new" "${archive}" + mv "${archive}.new" "${archive}" + fi +} + +# Normalize timestamps in tar archives +normalize_tar() { + local archive="$1" + local mtime="${SOURCE_DATE_EPOCH:-0}" + + # Repack with deterministic settings + local tmp_dir=$(mktemp -d) + tar -xf "${archive}" -C "${tmp_dir}" + tar --sort=name \ + --mtime="@${mtime}" \ + --owner=0 --group=0 \ + --numeric-owner \ + -cf "${archive}.new" -C "${tmp_dir}" . + mv "${archive}.new" "${archive}" + rm -rf "${tmp_dir}" +} + +# Normalize __pycache__ timestamps +normalize_python() { + find . -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true + find . -name '*.pyc' -delete 2>/dev/null || true +} + +# Strip build paths from binaries +strip_build_paths() { + local binary="$1" + if command -v objcopy &>/dev/null; then + # Remove .note.gnu.build-id if it contains build path + objcopy --remove-section=.note.gnu.build-id "${binary}" 2>/dev/null || true + fi +} + +# Main normalization +normalize_build() { + echo "Normalizing build environment..." + + # Normalize Python bytecode + normalize_python + + # Find and normalize archives + find . -name '*.a' -type f | while read -r ar; do + normalize_ar "${ar}" + done + + echo "Normalization complete" +} + +# If sourced, export functions; if executed, run normalization +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + normalize_build +fi diff --git a/devops/releases/service-versions.json b/devops/releases/service-versions.json new file mode 100644 index 000000000..3738b3722 --- /dev/null +++ b/devops/releases/service-versions.json @@ -0,0 +1,143 @@ +{ + "$schema": "./service-versions.schema.json", + "schemaVersion": "1.0.0", + "lastUpdated": "2025-01-01T00:00:00Z", + "registry": "git.stella-ops.org/stella-ops.org", + "services": { + "authority": { + "name": "Authority", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "attestor": { + "name": "Attestor", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "concelier": { + "name": "Concelier", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "scanner": { + "name": "Scanner", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "policy": { + "name": "Policy", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "signer": { + "name": "Signer", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "excititor": { + "name": "Excititor", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "gateway": { + "name": "Gateway", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "scheduler": { + "name": "Scheduler", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "cli": { + "name": "CLI", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "orchestrator": { + "name": "Orchestrator", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "notify": { + "name": "Notify", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "sbomservice": { + "name": "SbomService", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "vexhub": { + "name": "VexHub", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + }, + "evidencelocker": { + "name": "EvidenceLocker", + "version": "1.0.0", + "dockerTag": null, + "releasedAt": null, + "gitSha": null, + "sbomDigest": null, + "signatureDigest": null + } + } +} diff --git a/devops/scripts/efcore/Scaffold-AllModules.ps1 b/devops/scripts/efcore/Scaffold-AllModules.ps1 new file mode 100644 index 000000000..e8a202c87 --- /dev/null +++ b/devops/scripts/efcore/Scaffold-AllModules.ps1 @@ -0,0 +1,93 @@ +<# +.SYNOPSIS + Scaffolds EF Core DbContext, entities, and compiled models for all StellaOps modules. + +.DESCRIPTION + Iterates through all configured modules and runs Scaffold-Module.ps1 for each. + Use this after schema changes or for initial setup. + +.PARAMETER SkipMissing + Skip modules whose projects don't exist yet (default: true) + +.EXAMPLE + .\Scaffold-AllModules.ps1 + +.EXAMPLE + .\Scaffold-AllModules.ps1 -SkipMissing:$false +#> +param( + [bool]$SkipMissing = $true +) + +$ErrorActionPreference = "Stop" + +# Module definitions: Module name -> Schema name +$modules = @( + @{ Module = "Unknowns"; Schema = "unknowns" }, + @{ Module = "PacksRegistry"; Schema = "packs" }, + @{ Module = "Authority"; Schema = "authority" }, + @{ Module = "Scanner"; Schema = "scanner" }, + @{ Module = "Scheduler"; Schema = "scheduler" }, + @{ Module = "TaskRunner"; Schema = "taskrunner" }, + @{ Module = "Policy"; Schema = "policy" }, + @{ Module = "Notify"; Schema = "notify" }, + @{ Module = "Concelier"; Schema = "vuln" }, + @{ Module = "Excititor"; Schema = "vex" }, + @{ Module = "Signals"; Schema = "signals" }, + @{ Module = "Attestor"; Schema = "proofchain" }, + @{ Module = "Signer"; Schema = "signer" } +) + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = (Get-Item $ScriptDir).Parent.Parent.Parent.FullName + +Write-Host "" +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host " EF Core Scaffolding for All Modules" -ForegroundColor Cyan +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host "" + +$successCount = 0 +$skipCount = 0 +$failCount = 0 + +foreach ($m in $modules) { + $projectPath = Join-Path $RepoRoot "src" $m.Module "__Libraries" "StellaOps.$($m.Module).Persistence.EfCore" + + if (-not (Test-Path "$projectPath\*.csproj")) { + if ($SkipMissing) { + Write-Host "SKIP: $($m.Module) - Project not found" -ForegroundColor DarkGray + $skipCount++ + continue + } else { + Write-Host "FAIL: $($m.Module) - Project not found at: $projectPath" -ForegroundColor Red + $failCount++ + continue + } + } + + Write-Host "" + Write-Host ">>> Scaffolding $($m.Module)..." -ForegroundColor Magenta + + try { + & "$ScriptDir\Scaffold-Module.ps1" -Module $m.Module -Schema $m.Schema + $successCount++ + } + catch { + Write-Host "FAIL: $($m.Module) - $($_.Exception.Message)" -ForegroundColor Red + $failCount++ + } +} + +Write-Host "" +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host " Summary" -ForegroundColor Cyan +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host " Success: $successCount" +Write-Host " Skipped: $skipCount" +Write-Host " Failed: $failCount" +Write-Host "" + +if ($failCount -gt 0) { + exit 1 +} diff --git a/devops/scripts/efcore/Scaffold-Module.ps1 b/devops/scripts/efcore/Scaffold-Module.ps1 new file mode 100644 index 000000000..df7921487 --- /dev/null +++ b/devops/scripts/efcore/Scaffold-Module.ps1 @@ -0,0 +1,162 @@ +<# +.SYNOPSIS + Scaffolds EF Core DbContext, entities, and compiled models from PostgreSQL schema. + +.DESCRIPTION + This script performs database-first scaffolding for a StellaOps module: + 1. Cleans existing generated files (Entities, CompiledModels, DbContext) + 2. Scaffolds DbContext and entities from live PostgreSQL schema + 3. Generates compiled models for startup performance + +.PARAMETER Module + The module name (e.g., Unknowns, PacksRegistry, Authority) + +.PARAMETER Schema + The PostgreSQL schema name (defaults to lowercase module name) + +.PARAMETER ConnectionString + PostgreSQL connection string. If not provided, uses default dev connection. + +.PARAMETER ProjectPath + Optional custom project path. Defaults to src/{Module}/__Libraries/StellaOps.{Module}.Persistence.EfCore + +.EXAMPLE + .\Scaffold-Module.ps1 -Module Unknowns + +.EXAMPLE + .\Scaffold-Module.ps1 -Module Unknowns -Schema unknowns -ConnectionString "Host=localhost;Database=stellaops_platform;Username=unknowns_user;Password=unknowns_dev" + +.EXAMPLE + .\Scaffold-Module.ps1 -Module PacksRegistry -Schema packs +#> +param( + [Parameter(Mandatory=$true)] + [string]$Module, + + [string]$Schema, + + [string]$ConnectionString, + + [string]$ProjectPath +) + +$ErrorActionPreference = "Stop" + +# Resolve repository root +$RepoRoot = (Get-Item $PSScriptRoot).Parent.Parent.Parent.FullName + +# Default schema to lowercase module name +if (-not $Schema) { + $Schema = $Module.ToLower() +} + +# Default connection string +if (-not $ConnectionString) { + $user = "${Schema}_user" + $password = "${Schema}_dev" + $ConnectionString = "Host=localhost;Port=5432;Database=stellaops_platform;Username=$user;Password=$password;SearchPath=$Schema" +} + +# Default project path +if (-not $ProjectPath) { + $ProjectPath = Join-Path $RepoRoot "src" $Module "__Libraries" "StellaOps.$Module.Persistence.EfCore" +} + +$ContextDir = "Context" +$EntitiesDir = "Entities" +$CompiledModelsDir = "CompiledModels" + +Write-Host "" +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host " EF Core Scaffolding for Module: $Module" -ForegroundColor Cyan +Write-Host "============================================================================" -ForegroundColor Cyan +Write-Host " Schema: $Schema" +Write-Host " Project: $ProjectPath" +Write-Host " Connection: Host=localhost;Database=stellaops_platform;Username=${Schema}_user;..." +Write-Host "" + +# Verify project exists +if (-not (Test-Path "$ProjectPath\*.csproj")) { + Write-Error "Project not found at: $ProjectPath" + Write-Host "Create the project first with: dotnet new classlib -n StellaOps.$Module.Persistence.EfCore" + exit 1 +} + +# Step 1: Clean existing generated files +Write-Host "[1/4] Cleaning existing generated files..." -ForegroundColor Yellow +$paths = @( + (Join-Path $ProjectPath $EntitiesDir), + (Join-Path $ProjectPath $CompiledModelsDir), + (Join-Path $ProjectPath $ContextDir "${Module}DbContext.cs") +) +foreach ($path in $paths) { + if (Test-Path $path) { + Remove-Item -Recurse -Force $path + Write-Host " Removed: $path" -ForegroundColor DarkGray + } +} + +# Recreate directories +New-Item -ItemType Directory -Force -Path (Join-Path $ProjectPath $EntitiesDir) | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $ProjectPath $CompiledModelsDir) | Out-Null +New-Item -ItemType Directory -Force -Path (Join-Path $ProjectPath $ContextDir) | Out-Null + +# Step 2: Scaffold DbContext and entities +Write-Host "[2/4] Scaffolding DbContext and entities from schema '$Schema'..." -ForegroundColor Yellow +$scaffoldArgs = @( + "ef", "dbcontext", "scaffold", + "`"$ConnectionString`"", + "Npgsql.EntityFrameworkCore.PostgreSQL", + "--project", "`"$ProjectPath`"", + "--schema", $Schema, + "--context", "${Module}DbContext", + "--context-dir", $ContextDir, + "--output-dir", $EntitiesDir, + "--namespace", "StellaOps.$Module.Persistence.EfCore.Entities", + "--context-namespace", "StellaOps.$Module.Persistence.EfCore.Context", + "--data-annotations", + "--no-onconfiguring", + "--force" +) + +$process = Start-Process -FilePath "dotnet" -ArgumentList $scaffoldArgs -Wait -PassThru -NoNewWindow +if ($process.ExitCode -ne 0) { + Write-Error "Scaffold failed with exit code: $($process.ExitCode)" + exit 1 +} +Write-Host " Scaffolded entities to: $EntitiesDir" -ForegroundColor DarkGray + +# Step 3: Generate compiled models +Write-Host "[3/4] Generating compiled models..." -ForegroundColor Yellow +$optimizeArgs = @( + "ef", "dbcontext", "optimize", + "--project", "`"$ProjectPath`"", + "--context", "StellaOps.$Module.Persistence.EfCore.Context.${Module}DbContext", + "--output-dir", $CompiledModelsDir, + "--namespace", "StellaOps.$Module.Persistence.EfCore.CompiledModels" +) + +$process = Start-Process -FilePath "dotnet" -ArgumentList $optimizeArgs -Wait -PassThru -NoNewWindow +if ($process.ExitCode -ne 0) { + Write-Error "Compiled model generation failed with exit code: $($process.ExitCode)" + exit 1 +} +Write-Host " Generated compiled models to: $CompiledModelsDir" -ForegroundColor DarkGray + +# Step 4: Summary +Write-Host "[4/4] Scaffolding complete!" -ForegroundColor Green +Write-Host "" +Write-Host "Generated files:" -ForegroundColor Cyan +$contextFile = Join-Path $ProjectPath $ContextDir "${Module}DbContext.cs" +$entityFiles = Get-ChildItem -Path (Join-Path $ProjectPath $EntitiesDir) -Filter "*.cs" -ErrorAction SilentlyContinue +$compiledFiles = Get-ChildItem -Path (Join-Path $ProjectPath $CompiledModelsDir) -Filter "*.cs" -ErrorAction SilentlyContinue + +Write-Host " Context: $(if (Test-Path $contextFile) { $contextFile } else { 'Not found' })" +Write-Host " Entities: $($entityFiles.Count) files" +Write-Host " Compiled Models: $($compiledFiles.Count) files" +Write-Host "" +Write-Host "Next steps:" -ForegroundColor Yellow +Write-Host " 1. Review generated entities for any customization needs" +Write-Host " 2. Create repository implementations in Repositories/" +Write-Host " 3. Add DI registration in Extensions/" +Write-Host "" diff --git a/devops/scripts/efcore/scaffold-all-modules.sh b/devops/scripts/efcore/scaffold-all-modules.sh new file mode 100644 index 000000000..b2daf2719 --- /dev/null +++ b/devops/scripts/efcore/scaffold-all-modules.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# ============================================================================ +# EF Core Scaffolding for All StellaOps Modules +# ============================================================================ +# Iterates through all configured modules and runs scaffold-module.sh for each. +# Use this after schema changes or for initial setup. +# +# Usage: ./scaffold-all-modules.sh [--no-skip-missing] +# ============================================================================ + +set -e + +SKIP_MISSING=true +if [ "$1" = "--no-skip-missing" ]; then + SKIP_MISSING=false +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +# Module definitions: "Module:Schema" +MODULES=( + "Unknowns:unknowns" + "PacksRegistry:packs" + "Authority:authority" + "Scanner:scanner" + "Scheduler:scheduler" + "TaskRunner:taskrunner" + "Policy:policy" + "Notify:notify" + "Concelier:vuln" + "Excititor:vex" + "Signals:signals" + "Attestor:proofchain" + "Signer:signer" +) + +echo "" +echo "============================================================================" +echo " EF Core Scaffolding for All Modules" +echo "============================================================================" +echo "" + +SUCCESS_COUNT=0 +SKIP_COUNT=0 +FAIL_COUNT=0 + +for entry in "${MODULES[@]}"; do + MODULE="${entry%%:*}" + SCHEMA="${entry##*:}" + + PROJECT_PATH="$REPO_ROOT/src/$MODULE/__Libraries/StellaOps.$MODULE.Persistence.EfCore" + + if [ ! -f "$PROJECT_PATH"/*.csproj ]; then + if [ "$SKIP_MISSING" = true ]; then + echo "SKIP: $MODULE - Project not found" + ((SKIP_COUNT++)) + continue + else + echo "FAIL: $MODULE - Project not found at: $PROJECT_PATH" + ((FAIL_COUNT++)) + continue + fi + fi + + echo "" + echo ">>> Scaffolding $MODULE..." + + if "$SCRIPT_DIR/scaffold-module.sh" "$MODULE" "$SCHEMA"; then + ((SUCCESS_COUNT++)) + else + echo "FAIL: $MODULE - Scaffolding failed" + ((FAIL_COUNT++)) + fi +done + +echo "" +echo "============================================================================" +echo " Summary" +echo "============================================================================" +echo " Success: $SUCCESS_COUNT" +echo " Skipped: $SKIP_COUNT" +echo " Failed: $FAIL_COUNT" +echo "" + +if [ "$FAIL_COUNT" -gt 0 ]; then + exit 1 +fi diff --git a/devops/scripts/efcore/scaffold-module.sh b/devops/scripts/efcore/scaffold-module.sh new file mode 100644 index 000000000..9c6860c17 --- /dev/null +++ b/devops/scripts/efcore/scaffold-module.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# ============================================================================ +# EF Core Scaffolding Script for StellaOps Modules +# ============================================================================ +# Usage: ./scaffold-module.sh [Schema] [ConnectionString] +# +# Examples: +# ./scaffold-module.sh Unknowns +# ./scaffold-module.sh Unknowns unknowns +# ./scaffold-module.sh PacksRegistry packs "Host=localhost;..." +# ============================================================================ + +set -e + +MODULE=$1 +SCHEMA=${2:-$(echo "$MODULE" | tr '[:upper:]' '[:lower:]')} +CONNECTION_STRING=$3 + +if [ -z "$MODULE" ]; then + echo "Usage: $0 [Schema] [ConnectionString]" + echo "" + echo "Examples:" + echo " $0 Unknowns" + echo " $0 Unknowns unknowns" + echo " $0 PacksRegistry packs \"Host=localhost;Database=stellaops_platform;Username=packs_user;Password=packs_dev\"" + exit 1 +fi + +# Resolve repository root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +# Default connection string +if [ -z "$CONNECTION_STRING" ]; then + USER="${SCHEMA}_user" + PASSWORD="${SCHEMA}_dev" + CONNECTION_STRING="Host=localhost;Port=5432;Database=stellaops_platform;Username=$USER;Password=$PASSWORD;SearchPath=$SCHEMA" +fi + +PROJECT_DIR="$REPO_ROOT/src/$MODULE/__Libraries/StellaOps.$MODULE.Persistence.EfCore" +CONTEXT_DIR="Context" +ENTITIES_DIR="Entities" +COMPILED_DIR="CompiledModels" + +echo "" +echo "============================================================================" +echo " EF Core Scaffolding for Module: $MODULE" +echo "============================================================================" +echo " Schema: $SCHEMA" +echo " Project: $PROJECT_DIR" +echo " Connection: Host=localhost;Database=stellaops_platform;Username=${SCHEMA}_user;..." +echo "" + +# Verify project exists +if [ ! -f "$PROJECT_DIR"/*.csproj ]; then + echo "ERROR: Project not found at: $PROJECT_DIR" + echo "Create the project first with: dotnet new classlib -n StellaOps.$MODULE.Persistence.EfCore" + exit 1 +fi + +# Step 1: Clean existing generated files +echo "[1/4] Cleaning existing generated files..." +rm -rf "$PROJECT_DIR/$ENTITIES_DIR" +rm -rf "$PROJECT_DIR/$COMPILED_DIR" +rm -f "$PROJECT_DIR/$CONTEXT_DIR/${MODULE}DbContext.cs" + +mkdir -p "$PROJECT_DIR/$ENTITIES_DIR" +mkdir -p "$PROJECT_DIR/$COMPILED_DIR" +mkdir -p "$PROJECT_DIR/$CONTEXT_DIR" + +echo " Cleaned: $ENTITIES_DIR, $COMPILED_DIR, ${MODULE}DbContext.cs" + +# Step 2: Scaffold DbContext and entities +echo "[2/4] Scaffolding DbContext and entities from schema '$SCHEMA'..." +dotnet ef dbcontext scaffold \ + "$CONNECTION_STRING" \ + Npgsql.EntityFrameworkCore.PostgreSQL \ + --project "$PROJECT_DIR" \ + --schema "$SCHEMA" \ + --context "${MODULE}DbContext" \ + --context-dir "$CONTEXT_DIR" \ + --output-dir "$ENTITIES_DIR" \ + --namespace "StellaOps.$MODULE.Persistence.EfCore.Entities" \ + --context-namespace "StellaOps.$MODULE.Persistence.EfCore.Context" \ + --data-annotations \ + --no-onconfiguring \ + --force + +echo " Scaffolded entities to: $ENTITIES_DIR" + +# Step 3: Generate compiled models +echo "[3/4] Generating compiled models..." +dotnet ef dbcontext optimize \ + --project "$PROJECT_DIR" \ + --context "StellaOps.$MODULE.Persistence.EfCore.Context.${MODULE}DbContext" \ + --output-dir "$COMPILED_DIR" \ + --namespace "StellaOps.$MODULE.Persistence.EfCore.CompiledModels" + +echo " Generated compiled models to: $COMPILED_DIR" + +# Step 4: Summary +echo "[4/4] Scaffolding complete!" +echo "" +echo "Generated files:" +echo " Context: $PROJECT_DIR/$CONTEXT_DIR/${MODULE}DbContext.cs" +echo " Entities: $(ls -1 "$PROJECT_DIR/$ENTITIES_DIR"/*.cs 2>/dev/null | wc -l) files" +echo " Compiled Models: $(ls -1 "$PROJECT_DIR/$COMPILED_DIR"/*.cs 2>/dev/null | wc -l) files" +echo "" +echo "Next steps:" +echo " 1. Review generated entities for any customization needs" +echo " 2. Create repository implementations in Repositories/" +echo " 3. Add DI registration in Extensions/" +echo "" diff --git a/devops/scripts/fix-duplicate-packages.ps1 b/devops/scripts/fix-duplicate-packages.ps1 new file mode 100644 index 000000000..8578f8ed5 --- /dev/null +++ b/devops/scripts/fix-duplicate-packages.ps1 @@ -0,0 +1,100 @@ +#!/usr/bin/env pwsh +# fix-duplicate-packages.ps1 - Remove duplicate PackageReference items from test projects +# These are already provided by Directory.Build.props + +param([switch]$DryRun) + +$packagesToRemove = @( + "coverlet.collector", + "Microsoft.NET.Test.Sdk", + "Microsoft.AspNetCore.Mvc.Testing", + "xunit", + "xunit.runner.visualstudio", + "Microsoft.Extensions.TimeProvider.Testing" +) + +$sharpCompressPackage = "SharpCompress" + +# Find all test project files +$testProjects = Get-ChildItem -Path "src" -Filter "*.Tests.csproj" -Recurse +$corpusProjects = Get-ChildItem -Path "src" -Filter "*.Corpus.*.csproj" -Recurse + +Write-Host "=== Fix Duplicate Package References ===" -ForegroundColor Cyan +Write-Host "Found $($testProjects.Count) test projects" -ForegroundColor Yellow +Write-Host "Found $($corpusProjects.Count) corpus projects (SharpCompress)" -ForegroundColor Yellow + +$fixedCount = 0 + +foreach ($proj in $testProjects) { + $content = Get-Content $proj.FullName -Raw + $modified = $false + + # Skip projects that opt out of common test infrastructure + if ($content -match "\s*false\s*") { + Write-Host " Skipped (UseConcelierTestInfra=false): $($proj.Name)" -ForegroundColor DarkGray + continue + } + + foreach ($pkg in $packagesToRemove) { + # Match PackageReference for this package (various formats) + $patterns = @( + "(?s)\s*\r?\n?", + "(?s)\s*\s*\r?\n?" + ) + + foreach ($pattern in $patterns) { + if ($content -match $pattern) { + $content = $content -replace $pattern, "" + $modified = $true + } + } + } + + # Clean up empty ItemGroups + $content = $content -replace "(?s)\s*\s*", "" + # Clean up ItemGroups with only whitespace/comments + $content = $content -replace "(?s)\s*\s*", "" + + if ($modified) { + $fixedCount++ + Write-Host " Fixed: $($proj.Name)" -ForegroundColor Green + if (-not $DryRun) { + $content | Set-Content $proj.FullName -NoNewline + } + } +} + +# Fix SharpCompress in corpus projects +foreach ($proj in $corpusProjects) { + $content = Get-Content $proj.FullName -Raw + $modified = $false + + $patterns = @( + "(?s)\s*\r?\n?", + "(?s)\s*\s*\r?\n?" + ) + + foreach ($pattern in $patterns) { + if ($content -match $pattern) { + $content = $content -replace $pattern, "" + $modified = $true + } + } + + # Clean up empty ItemGroups + $content = $content -replace "(?s)\s*\s*", "" + + if ($modified) { + $fixedCount++ + Write-Host " Fixed: $($proj.Name)" -ForegroundColor Green + if (-not $DryRun) { + $content | Set-Content $proj.FullName -NoNewline + } + } +} + +Write-Host "" +Write-Host "Fixed $fixedCount projects" -ForegroundColor Cyan +if ($DryRun) { + Write-Host "(Dry run - no changes made)" -ForegroundColor Yellow +} diff --git a/devops/scripts/fix-duplicate-projects.ps1 b/devops/scripts/fix-duplicate-projects.ps1 new file mode 100644 index 000000000..d14d55d5d --- /dev/null +++ b/devops/scripts/fix-duplicate-projects.ps1 @@ -0,0 +1,55 @@ +#!/usr/bin/env pwsh +# fix-duplicate-projects.ps1 - Remove duplicate project entries from solution file + +param( + [string]$SlnPath = "src/StellaOps.sln" +) + +$content = Get-Content $SlnPath -Raw +$lines = $content -split "`r?`n" + +$projectNames = @{} +$duplicateGuids = @() +$newLines = @() +$skipNextEndProject = $false + +foreach ($line in $lines) { + if ($skipNextEndProject -and $line -eq "EndProject") { + $skipNextEndProject = $false + continue + } + + if ($line -match 'Project\(.+\) = "([^"]+)",.*\{([A-F0-9-]+)\}"?$') { + $name = $Matches[1] + $guid = $Matches[2] + + if ($projectNames.ContainsKey($name)) { + $duplicateGuids += $guid + Write-Host "Removing duplicate: $name ($guid)" + $skipNextEndProject = $true + continue + } else { + $projectNames[$name] = $true + } + } + + $newLines += $line +} + +# Also remove duplicate GUIDs from GlobalSection +$finalLines = @() +foreach ($line in $newLines) { + $skip = $false + foreach ($guid in $duplicateGuids) { + if ($line -match $guid) { + $skip = $true + break + } + } + if (-not $skip) { + $finalLines += $line + } +} + +$finalLines | Out-File -FilePath $SlnPath -Encoding UTF8 -NoNewline +Write-Host "`nRemoved $($duplicateGuids.Count) duplicate projects" diff --git a/devops/scripts/fix-duplicate-using-testkit.ps1 b/devops/scripts/fix-duplicate-using-testkit.ps1 new file mode 100644 index 000000000..8350032dc --- /dev/null +++ b/devops/scripts/fix-duplicate-using-testkit.ps1 @@ -0,0 +1,55 @@ +# Fix duplicate "using StellaOps.TestKit;" statements in C# files +# The pattern shows files have this statement both at top (correct) and in middle (wrong) +# This script removes all occurrences AFTER the first one + +$ErrorActionPreference = "Stop" + +$srcPath = Join-Path $PSScriptRoot "..\..\src" +$pattern = "using StellaOps.TestKit;" + +# Find all .cs files containing the pattern +$files = Get-ChildItem -Path $srcPath -Recurse -Filter "*.cs" | + Where-Object { (Get-Content $_.FullName -Raw) -match [regex]::Escape($pattern) } + +Write-Host "Found $($files.Count) files with 'using StellaOps.TestKit;'" -ForegroundColor Cyan + +$fixedCount = 0 +$errorCount = 0 + +foreach ($file in $files) { + try { + $lines = Get-Content $file.FullName + $newLines = @() + $foundFirst = $false + $removedAny = $false + + foreach ($line in $lines) { + if ($line.Trim() -eq $pattern) { + if (-not $foundFirst) { + # Keep the first occurrence + $newLines += $line + $foundFirst = $true + } else { + # Skip subsequent occurrences + $removedAny = $true + } + } else { + $newLines += $line + } + } + + if ($removedAny) { + $newLines | Set-Content -Path $file.FullName -Encoding UTF8 + Write-Host "Fixed: $($file.Name)" -ForegroundColor Green + $fixedCount++ + } + } catch { + Write-Host "Error processing $($file.FullName): $_" -ForegroundColor Red + $errorCount++ + } +} + +Write-Host "" +Write-Host "Summary:" -ForegroundColor Cyan +Write-Host " Files fixed: $fixedCount" -ForegroundColor Green +Write-Host " Errors: $errorCount" -ForegroundColor $(if ($errorCount -gt 0) { "Red" } else { "Green" }) diff --git a/devops/scripts/fix-missing-xunit.ps1 b/devops/scripts/fix-missing-xunit.ps1 new file mode 100644 index 000000000..f2920b945 --- /dev/null +++ b/devops/scripts/fix-missing-xunit.ps1 @@ -0,0 +1,51 @@ +# Fix projects with UseConcelierTestInfra=false that don't have xunit +# These projects relied on TestKit for xunit, but now need their own reference + +$ErrorActionPreference = "Stop" +$srcPath = "E:\dev\git.stella-ops.org\src" + +# Find test projects with UseConcelierTestInfra=false +$projects = Get-ChildItem -Path $srcPath -Recurse -Filter "*.csproj" | + Where-Object { + $content = Get-Content $_.FullName -Raw + ($content -match "\s*false\s*") -and + (-not ($content -match "xunit\.v3")) -and # Skip xunit.v3 projects + (-not ($content -match ' + + +'@ + +$fixedCount = 0 + +foreach ($proj in $projects) { + $content = Get-Content $proj.FullName -Raw + + # Check if it has an ItemGroup with PackageReference + if ($content -match '([\s\S]*?\s*\r?\n)(\s* + $itemGroup = @" + + +$xunitPackages + +"@ + $newContent = $content -replace '', "$itemGroup`n" + } + + if ($newContent -ne $content) { + Set-Content -Path $proj.FullName -Value $newContent -NoNewline + Write-Host "Fixed: $($proj.Name)" -ForegroundColor Green + $fixedCount++ + } +} + +Write-Host "`nFixed $fixedCount projects" -ForegroundColor Cyan diff --git a/devops/scripts/fix-project-references.ps1 b/devops/scripts/fix-project-references.ps1 new file mode 100644 index 000000000..a193d11eb --- /dev/null +++ b/devops/scripts/fix-project-references.ps1 @@ -0,0 +1,44 @@ +# Fix project references in src/__Tests/** that point to wrong relative paths +# Pattern: ../..//... should be ../../..//... + +$ErrorActionPreference = "Stop" +$testsPath = "E:\dev\git.stella-ops.org\src\__Tests" + +# Known module prefixes that exist at src// +$modules = @("Signals", "Scanner", "Concelier", "Scheduler", "Authority", "Attestor", + "BinaryIndex", "EvidenceLocker", "Excititor", "ExportCenter", "Gateway", + "Graph", "IssuerDirectory", "Notify", "Orchestrator", "Policy", "AirGap", + "Provenance", "Replay", "RiskEngine", "SbomService", "Signer", "TaskRunner", + "Telemetry", "TimelineIndexer", "Unknowns", "VexHub", "VexLens", "VulnExplorer", + "Zastava", "Cli", "Aoc", "Web", "Bench", "Cryptography", "PacksRegistry", + "Notifier", "Findings") + +$fixedCount = 0 + +Get-ChildItem -Path $testsPath -Recurse -Filter "*.csproj" | ForEach-Object { + $proj = $_ + $content = Get-Content $proj.FullName -Raw + $originalContent = $content + + foreach ($module in $modules) { + # Fix ../..// to ../../..// + # But not ../../../ (already correct) + $pattern = "Include=`"../../$module/" + $replacement = "Include=`"../../../$module/" + + if ($content -match [regex]::Escape($pattern) -and $content -notmatch [regex]::Escape("Include=`"../../../$module/")) { + $content = $content -replace [regex]::Escape($pattern), $replacement + } + } + + # Fix __Libraries references that are one level short + $content = $content -replace 'Include="../../__Libraries/', 'Include="../../../__Libraries/' + + if ($content -ne $originalContent) { + Set-Content -Path $proj.FullName -Value $content -NoNewline + Write-Host "Fixed: $($proj.Name)" -ForegroundColor Green + $fixedCount++ + } +} + +Write-Host "`nFixed $fixedCount projects" -ForegroundColor Cyan diff --git a/devops/scripts/fix-sln-duplicates.ps1 b/devops/scripts/fix-sln-duplicates.ps1 new file mode 100644 index 000000000..c0dae4b5d --- /dev/null +++ b/devops/scripts/fix-sln-duplicates.ps1 @@ -0,0 +1,68 @@ +#!/usr/bin/env pwsh +# fix-sln-duplicates.ps1 - Remove duplicate project entries from solution file + +param( + [string]$SlnPath = "src/StellaOps.sln" +) + +$ErrorActionPreference = "Stop" + +Write-Host "=== Solution Duplicate Cleanup ===" -ForegroundColor Cyan +Write-Host "Solution: $SlnPath" + +$content = Get-Content $SlnPath -Raw +$lines = $content -split "`r?`n" + +# Track seen project names +$seenProjects = @{} +$duplicateGuids = @() +$newLines = @() +$skipNext = $false + +for ($i = 0; $i -lt $lines.Count; $i++) { + $line = $lines[$i] + + if ($skipNext) { + $skipNext = $false + continue + } + + # Check for project declaration + if ($line -match 'Project\(.+\) = "([^"]+)",.*\{([A-F0-9-]+)\}"?$') { + $name = $Matches[1] + $guid = $Matches[2] + + if ($seenProjects.ContainsKey($name)) { + Write-Host "Removing duplicate: $name ($guid)" -ForegroundColor Yellow + $duplicateGuids += $guid + # Skip this line and the next EndProject line + $skipNext = $true + continue + } else { + $seenProjects[$name] = $true + } + } + + $newLines += $line +} + +# Remove GlobalSection references to duplicate GUIDs +$finalLines = @() +foreach ($line in $newLines) { + $skip = $false + foreach ($guid in $duplicateGuids) { + if ($line -match $guid) { + $skip = $true + break + } + } + if (-not $skip) { + $finalLines += $line + } +} + +# Write back +$finalLines -join "`r`n" | Set-Content $SlnPath -Encoding UTF8 -NoNewline + +Write-Host "" +Write-Host "Removed $($duplicateGuids.Count) duplicate projects" -ForegroundColor Green diff --git a/devops/scripts/fix-xunit-using.ps1 b/devops/scripts/fix-xunit-using.ps1 new file mode 100644 index 000000000..55be3448d --- /dev/null +++ b/devops/scripts/fix-xunit-using.ps1 @@ -0,0 +1,40 @@ +# Add to test projects with UseConcelierTestInfra=false +# that have xunit but don't have the global using + +$ErrorActionPreference = "Stop" +$srcPath = "E:\dev\git.stella-ops.org\src" + +# Find test projects with UseConcelierTestInfra=false that have xunit but no Using Include="Xunit" +$projects = Get-ChildItem -Path $srcPath -Recurse -Filter "*.csproj" | + Where-Object { + $content = Get-Content $_.FullName -Raw + ($content -match "\s*false\s*") -and + ($content -match '\s*\r?\n\s*`n `n`n" + $newContent = $content -replace '(\s*)(\s*\r?\n\s* + $usingBlock = "`n `n `n `n" + $newContent = $content -replace '', "$usingBlock" + } + + if ($newContent -ne $content) { + Set-Content -Path $proj.FullName -Value $newContent -NoNewline + Write-Host "Fixed: $($proj.Name)" -ForegroundColor Green + $fixedCount++ + } +} + +Write-Host "`nFixed $fixedCount projects" -ForegroundColor Cyan diff --git a/devops/scripts/fix-xunit-v3-conflict.ps1 b/devops/scripts/fix-xunit-v3-conflict.ps1 new file mode 100644 index 000000000..72d34336d --- /dev/null +++ b/devops/scripts/fix-xunit-v3-conflict.ps1 @@ -0,0 +1,37 @@ +# Fix xunit.v3 projects that conflict with Directory.Build.props xunit 2.x +# Add UseConcelierTestInfra=false to exclude them from common test infrastructure + +$ErrorActionPreference = "Stop" + +$srcPath = Join-Path $PSScriptRoot "..\..\src" + +# Find all csproj files that reference xunit.v3 +$xunitV3Projects = Get-ChildItem -Path $srcPath -Recurse -Filter "*.csproj" | + Where-Object { (Get-Content $_.FullName -Raw) -match "xunit\.v3" } + +Write-Host "Found $($xunitV3Projects.Count) projects with xunit.v3" -ForegroundColor Cyan + +$fixedCount = 0 + +foreach ($proj in $xunitV3Projects) { + $content = Get-Content $proj.FullName -Raw + + # Check if already has UseConcelierTestInfra set + if ($content -match "") { + Write-Host " Skipped (already configured): $($proj.Name)" -ForegroundColor DarkGray + continue + } + + # Add UseConcelierTestInfra=false after the first + $newContent = $content -replace "()", "`$1`n false" + + # Only write if changed + if ($newContent -ne $content) { + Set-Content -Path $proj.FullName -Value $newContent -NoNewline + Write-Host " Fixed: $($proj.Name)" -ForegroundColor Green + $fixedCount++ + } +} + +Write-Host "" +Write-Host "Fixed $fixedCount projects" -ForegroundColor Cyan diff --git a/devops/scripts/generate-plugin-configs.ps1 b/devops/scripts/generate-plugin-configs.ps1 new file mode 100644 index 000000000..7a0f7721f --- /dev/null +++ b/devops/scripts/generate-plugin-configs.ps1 @@ -0,0 +1,247 @@ +<# +.SYNOPSIS + Generates plugin configuration files for StellaOps modules. + +.DESCRIPTION + This script generates plugin.json manifests and config.yaml files for all + plugins based on the plugin catalog definition. + +.PARAMETER RepoRoot + Path to the repository root. Defaults to the parent of the devops folder. + +.PARAMETER OutputDir + Output directory for generated configs. Defaults to etc/plugins/. + +.PARAMETER Force + Overwrite existing configuration files. + +.EXAMPLE + .\generate-plugin-configs.ps1 + .\generate-plugin-configs.ps1 -Force +#> + +param( + [string]$RepoRoot = (Split-Path -Parent (Split-Path -Parent $PSScriptRoot)), + [string]$OutputDir = "", + [switch]$Force +) + +if (-not $OutputDir) { + $OutputDir = Join-Path $RepoRoot "etc/plugins" +} + +# Plugin catalog - defines all plugins and their metadata +$PluginCatalog = @{ + # Router transports + "router/transports" = @{ + category = "router.transports" + plugins = @( + @{ id = "tcp"; name = "TCP Transport"; assembly = "StellaOps.Router.Transport.Tcp.dll"; enabled = $true; priority = 50 } + @{ id = "tls"; name = "TLS Transport"; assembly = "StellaOps.Router.Transport.Tls.dll"; enabled = $true; priority = 60 } + @{ id = "udp"; name = "UDP Transport"; assembly = "StellaOps.Router.Transport.Udp.dll"; enabled = $false; priority = 40 } + @{ id = "rabbitmq"; name = "RabbitMQ Transport"; assembly = "StellaOps.Router.Transport.RabbitMq.dll"; enabled = $false; priority = 30 } + @{ id = "inmemory"; name = "In-Memory Transport"; assembly = "StellaOps.Router.Transport.InMemory.dll"; enabled = $false; priority = 10 } + ) + } + + # Excititor connectors + "excititor" = @{ + category = "excititor.connectors" + plugins = @( + @{ id = "redhat-csaf"; name = "Red Hat CSAF Connector"; assembly = "StellaOps.Excititor.Connectors.RedHat.CSAF.dll"; enabled = $true; priority = 100; vendor = "Red Hat" } + @{ id = "cisco-csaf"; name = "Cisco CSAF Connector"; assembly = "StellaOps.Excititor.Connectors.Cisco.CSAF.dll"; enabled = $false; priority = 90; vendor = "Cisco" } + @{ id = "msrc-csaf"; name = "Microsoft CSAF Connector"; assembly = "StellaOps.Excititor.Connectors.MSRC.CSAF.dll"; enabled = $false; priority = 85; vendor = "Microsoft" } + @{ id = "oracle-csaf"; name = "Oracle CSAF Connector"; assembly = "StellaOps.Excititor.Connectors.Oracle.CSAF.dll"; enabled = $false; priority = 80; vendor = "Oracle" } + @{ id = "ubuntu-csaf"; name = "Ubuntu CSAF Connector"; assembly = "StellaOps.Excititor.Connectors.Ubuntu.CSAF.dll"; enabled = $false; priority = 75; vendor = "Canonical" } + @{ id = "suse-rancher"; name = "SUSE Rancher VEX Hub"; assembly = "StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.dll"; enabled = $false; priority = 70; vendor = "SUSE" } + @{ id = "oci-openvex"; name = "OCI OpenVEX Connector"; assembly = "StellaOps.Excititor.Connectors.OCI.OpenVEX.Attest.dll"; enabled = $false; priority = 60 } + ) + } + + # Scanner language analyzers + "scanner/analyzers/lang" = @{ + category = "scanner.analyzers.lang" + plugins = @( + @{ id = "dotnet"; name = ".NET Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.DotNet.dll"; enabled = $true; priority = 100 } + @{ id = "go"; name = "Go Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Go.dll"; enabled = $true; priority = 95 } + @{ id = "node"; name = "Node.js Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Node.dll"; enabled = $true; priority = 90 } + @{ id = "python"; name = "Python Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Python.dll"; enabled = $true; priority = 85 } + @{ id = "java"; name = "Java Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Java.dll"; enabled = $true; priority = 80 } + @{ id = "rust"; name = "Rust Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Rust.dll"; enabled = $false; priority = 75 } + @{ id = "ruby"; name = "Ruby Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Ruby.dll"; enabled = $false; priority = 70 } + @{ id = "php"; name = "PHP Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Php.dll"; enabled = $false; priority = 65 } + @{ id = "swift"; name = "Swift Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Swift.dll"; enabled = $false; priority = 60 } + @{ id = "cpp"; name = "C/C++ Analyzer"; assembly = "StellaOps.Scanner.Analyzers.Lang.Cpp.dll"; enabled = $false; priority = 55 } + ) + } + + # Scanner OS analyzers + "scanner/analyzers/os" = @{ + category = "scanner.analyzers.os" + plugins = @( + @{ id = "apk"; name = "Alpine APK Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Apk.dll"; enabled = $true; priority = 100 } + @{ id = "dpkg"; name = "Debian DPKG Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Dpkg.dll"; enabled = $true; priority = 95 } + @{ id = "rpm"; name = "RPM Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Rpm.dll"; enabled = $true; priority = 90 } + @{ id = "pacman"; name = "Arch Pacman Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Pacman.dll"; enabled = $false; priority = 80 } + @{ id = "homebrew"; name = "Homebrew Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Homebrew.dll"; enabled = $false; priority = 70 } + @{ id = "chocolatey"; name = "Chocolatey Analyzer"; assembly = "StellaOps.Scanner.Analyzers.OS.Chocolatey.dll"; enabled = $false; priority = 65 } + ) + } + + # Notify channels + "notify" = @{ + category = "notify.channels" + plugins = @( + @{ id = "email"; name = "Email Notifier"; assembly = "StellaOps.Notify.Connectors.Email.dll"; enabled = $true; priority = 100 } + @{ id = "slack"; name = "Slack Notifier"; assembly = "StellaOps.Notify.Connectors.Slack.dll"; enabled = $true; priority = 90 } + @{ id = "webhook"; name = "Webhook Notifier"; assembly = "StellaOps.Notify.Connectors.Webhook.dll"; enabled = $true; priority = 80 } + @{ id = "teams"; name = "Microsoft Teams Notifier"; assembly = "StellaOps.Notify.Connectors.Teams.dll"; enabled = $false; priority = 85 } + @{ id = "pagerduty"; name = "PagerDuty Notifier"; assembly = "StellaOps.Notify.Connectors.PagerDuty.dll"; enabled = $false; priority = 75 } + @{ id = "opsgenie"; name = "OpsGenie Notifier"; assembly = "StellaOps.Notify.Connectors.OpsGenie.dll"; enabled = $false; priority = 70 } + @{ id = "telegram"; name = "Telegram Notifier"; assembly = "StellaOps.Notify.Connectors.Telegram.dll"; enabled = $false; priority = 65 } + @{ id = "discord"; name = "Discord Notifier"; assembly = "StellaOps.Notify.Connectors.Discord.dll"; enabled = $false; priority = 60 } + ) + } + + # Messaging transports + "messaging" = @{ + category = "messaging.transports" + plugins = @( + @{ id = "valkey"; name = "Valkey Transport"; assembly = "StellaOps.Messaging.Transport.Valkey.dll"; enabled = $true; priority = 100 } + @{ id = "postgres"; name = "PostgreSQL Transport"; assembly = "StellaOps.Messaging.Transport.Postgres.dll"; enabled = $false; priority = 90 } + @{ id = "inmemory"; name = "In-Memory Transport"; assembly = "StellaOps.Messaging.Transport.InMemory.dll"; enabled = $false; priority = 10 } + ) + } +} + +function New-PluginManifest { + param( + [string]$ModulePath, + [hashtable]$Plugin, + [string]$Category + ) + + $fullId = "stellaops.$($Category.Replace('/', '.').Replace('.', '-')).$($Plugin.id)" + + $manifest = @{ + '$schema' = "https://schema.stella-ops.org/plugin-manifest/v2.json" + schemaVersion = "2.0" + id = $fullId + name = $Plugin.name + version = "1.0.0" + assembly = @{ + path = $Plugin.assembly + } + capabilities = @() + platforms = @("linux-x64", "linux-arm64", "win-x64", "osx-x64", "osx-arm64") + compliance = @("NIST") + jurisdiction = "world" + priority = $Plugin.priority + enabled = $Plugin.enabled + metadata = @{ + author = "StellaOps" + license = "AGPL-3.0-or-later" + } + } + + if ($Plugin.vendor) { + $manifest.metadata["vendor"] = $Plugin.vendor + } + + return $manifest | ConvertTo-Json -Depth 10 +} + +function New-PluginConfig { + param( + [string]$ModulePath, + [hashtable]$Plugin, + [string]$Category + ) + + $fullId = "stellaops.$($Category.Replace('/', '.').Replace('.', '-')).$($Plugin.id)" + + $config = @" +id: $fullId +name: $($Plugin.name) +enabled: $($Plugin.enabled.ToString().ToLower()) +priority: $($Plugin.priority) +config: + # Plugin-specific configuration + # Add settings here as needed +"@ + + return $config +} + +function New-RegistryFile { + param( + [string]$Category, + [array]$Plugins + ) + + $entries = $Plugins | ForEach-Object { + " $($_.id):`n enabled: $($_.enabled.ToString().ToLower())`n priority: $($_.priority)`n config: $($_.id)/config.yaml" + } + + $registry = @" +version: "1.0" +category: $Category +defaults: + enabled: false + timeout: "00:05:00" +plugins: +$($entries -join "`n") +"@ + + return $registry +} + +# Main generation logic +Write-Host "Generating plugin configurations to: $OutputDir" -ForegroundColor Cyan + +foreach ($modulePath in $PluginCatalog.Keys) { + $moduleConfig = $PluginCatalog[$modulePath] + $moduleDir = Join-Path $OutputDir $modulePath + + Write-Host "Processing module: $modulePath" -ForegroundColor Yellow + + # Create module directory + if (-not (Test-Path $moduleDir)) { + New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null + } + + # Generate registry.yaml + $registryPath = Join-Path $moduleDir "registry.yaml" + if ($Force -or -not (Test-Path $registryPath)) { + $registryContent = New-RegistryFile -Category $moduleConfig.category -Plugins $moduleConfig.plugins + Set-Content -Path $registryPath -Value $registryContent -Encoding utf8 + Write-Host " Created: registry.yaml" -ForegroundColor Green + } + + # Generate plugin configs + foreach ($plugin in $moduleConfig.plugins) { + $pluginDir = Join-Path $moduleDir $plugin.id + + if (-not (Test-Path $pluginDir)) { + New-Item -ItemType Directory -Path $pluginDir -Force | Out-Null + } + + # plugin.json + $manifestPath = Join-Path $pluginDir "plugin.json" + if ($Force -or -not (Test-Path $manifestPath)) { + $manifestContent = New-PluginManifest -ModulePath $modulePath -Plugin $plugin -Category $moduleConfig.category + Set-Content -Path $manifestPath -Value $manifestContent -Encoding utf8 + Write-Host " Created: $($plugin.id)/plugin.json" -ForegroundColor Green + } + + # config.yaml + $configPath = Join-Path $pluginDir "config.yaml" + if ($Force -or -not (Test-Path $configPath)) { + $configContent = New-PluginConfig -ModulePath $modulePath -Plugin $plugin -Category $moduleConfig.category + Set-Content -Path $configPath -Value $configContent -Encoding utf8 + Write-Host " Created: $($plugin.id)/config.yaml" -ForegroundColor Green + } + } +} + +Write-Host "`nPlugin configuration generation complete!" -ForegroundColor Cyan diff --git a/devops/scripts/lib/ci-common.sh b/devops/scripts/lib/ci-common.sh new file mode 100644 index 000000000..4863502ff --- /dev/null +++ b/devops/scripts/lib/ci-common.sh @@ -0,0 +1,406 @@ +#!/usr/bin/env bash +# ============================================================================= +# CI COMMON FUNCTIONS +# ============================================================================= +# Shared utility functions for local CI testing scripts. +# +# Usage: +# source "$SCRIPT_DIR/lib/ci-common.sh" +# +# ============================================================================= + +# Prevent multiple sourcing +[[ -n "${_CI_COMMON_LOADED:-}" ]] && return +_CI_COMMON_LOADED=1 + +# ============================================================================= +# COLOR DEFINITIONS +# ============================================================================= + +if [[ -t 1 ]] && [[ -n "${TERM:-}" ]] && [[ "${TERM}" != "dumb" ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + BLUE='\033[0;34m' + MAGENTA='\033[0;35m' + CYAN='\033[0;36m' + WHITE='\033[0;37m' + BOLD='\033[1m' + DIM='\033[2m' + RESET='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + MAGENTA='' + CYAN='' + WHITE='' + BOLD='' + DIM='' + RESET='' +fi + +# ============================================================================= +# LOGGING FUNCTIONS +# ============================================================================= + +# Log an info message +log_info() { + echo -e "${BLUE}[INFO]${RESET} $*" +} + +# Log a success message +log_success() { + echo -e "${GREEN}[OK]${RESET} $*" +} + +# Log a warning message +log_warn() { + echo -e "${YELLOW}[WARN]${RESET} $*" >&2 +} + +# Log an error message +log_error() { + echo -e "${RED}[ERROR]${RESET} $*" >&2 +} + +# Log a debug message (only if VERBOSE is true) +log_debug() { + if [[ "${VERBOSE:-false}" == "true" ]]; then + echo -e "${DIM}[DEBUG]${RESET} $*" + fi +} + +# Log a step in a process +log_step() { + local step_num="$1" + local total_steps="$2" + local message="$3" + echo -e "${CYAN}[${step_num}/${total_steps}]${RESET} ${BOLD}${message}${RESET}" +} + +# Log a section header +log_section() { + echo "" + echo -e "${BOLD}${MAGENTA}=== $* ===${RESET}" + echo "" +} + +# Log a subsection header +log_subsection() { + echo -e "${CYAN}--- $* ---${RESET}" +} + +# ============================================================================= +# ERROR HANDLING +# ============================================================================= + +# Exit with error message +die() { + log_error "$@" + exit 1 +} + +# Check if a command exists +require_command() { + local cmd="$1" + local install_hint="${2:-}" + + if ! command -v "$cmd" &>/dev/null; then + log_error "Required command not found: $cmd" + if [[ -n "$install_hint" ]]; then + log_info "Install with: $install_hint" + fi + return 1 + fi + return 0 +} + +# Check if a file exists +require_file() { + local file="$1" + if [[ ! -f "$file" ]]; then + log_error "Required file not found: $file" + return 1 + fi + return 0 +} + +# Check if a directory exists +require_dir() { + local dir="$1" + if [[ ! -d "$dir" ]]; then + log_error "Required directory not found: $dir" + return 1 + fi + return 0 +} + +# ============================================================================= +# TIMING FUNCTIONS +# ============================================================================= + +# Get current timestamp in seconds +get_timestamp() { + date +%s +} + +# Format duration in human-readable format +format_duration() { + local seconds="$1" + local minutes=$((seconds / 60)) + local remaining_seconds=$((seconds % 60)) + + if [[ $minutes -gt 0 ]]; then + echo "${minutes}m ${remaining_seconds}s" + else + echo "${remaining_seconds}s" + fi +} + +# Start a timer and return the start time +start_timer() { + get_timestamp +} + +# Stop a timer and print the duration +stop_timer() { + local start_time="$1" + local label="${2:-Operation}" + local end_time + end_time=$(get_timestamp) + local duration=$((end_time - start_time)) + + log_info "$label completed in $(format_duration $duration)" +} + +# ============================================================================= +# STRING FUNCTIONS +# ============================================================================= + +# Convert string to lowercase +to_lower() { + echo "$1" | tr '[:upper:]' '[:lower:]' +} + +# Convert string to uppercase +to_upper() { + echo "$1" | tr '[:lower:]' '[:upper:]' +} + +# Trim whitespace from string +trim() { + local var="$*" + var="${var#"${var%%[![:space:]]*}"}" + var="${var%"${var##*[![:space:]]}"}" + echo -n "$var" +} + +# Join array elements with delimiter +join_by() { + local delimiter="$1" + shift + local first="$1" + shift + printf '%s' "$first" "${@/#/$delimiter}" +} + +# ============================================================================= +# ARRAY FUNCTIONS +# ============================================================================= + +# Check if array contains element +array_contains() { + local needle="$1" + shift + local element + for element in "$@"; do + [[ "$element" == "$needle" ]] && return 0 + done + return 1 +} + +# ============================================================================= +# FILE FUNCTIONS +# ============================================================================= + +# Create directory if it doesn't exist +ensure_dir() { + local dir="$1" + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir" + log_debug "Created directory: $dir" + fi +} + +# Get absolute path +get_absolute_path() { + local path="$1" + if [[ -d "$path" ]]; then + (cd "$path" && pwd) + elif [[ -f "$path" ]]; then + local dir + dir=$(dirname "$path") + echo "$(cd "$dir" && pwd)/$(basename "$path")" + else + echo "$path" + fi +} + +# ============================================================================= +# GIT FUNCTIONS +# ============================================================================= + +# Get the repository root directory +get_repo_root() { + git rev-parse --show-toplevel 2>/dev/null +} + +# Get current branch name +get_current_branch() { + git rev-parse --abbrev-ref HEAD 2>/dev/null +} + +# Get current commit SHA +get_current_sha() { + git rev-parse HEAD 2>/dev/null +} + +# Get short commit SHA +get_short_sha() { + git rev-parse --short HEAD 2>/dev/null +} + +# Check if working directory is clean +is_git_clean() { + [[ -z "$(git status --porcelain 2>/dev/null)" ]] +} + +# Get list of changed files compared to main branch +get_changed_files() { + local base_branch="${1:-main}" + git diff --name-only "$base_branch"...HEAD 2>/dev/null +} + +# ============================================================================= +# MODULE DETECTION +# ============================================================================= + +# Map of module names to source paths +declare -A MODULE_PATHS=( + ["Scanner"]="src/Scanner src/BinaryIndex" + ["Concelier"]="src/Concelier src/Excititor" + ["Authority"]="src/Authority" + ["Policy"]="src/Policy src/RiskEngine" + ["Attestor"]="src/Attestor src/Provenance" + ["EvidenceLocker"]="src/EvidenceLocker" + ["ExportCenter"]="src/ExportCenter" + ["Findings"]="src/Findings" + ["SbomService"]="src/SbomService" + ["Notify"]="src/Notify src/Notifier" + ["Router"]="src/Router src/Gateway" + ["Cryptography"]="src/Cryptography" + ["AirGap"]="src/AirGap" + ["Cli"]="src/Cli" + ["AdvisoryAI"]="src/AdvisoryAI" + ["ReachGraph"]="src/ReachGraph" + ["Orchestrator"]="src/Orchestrator" + ["PacksRegistry"]="src/PacksRegistry" + ["Replay"]="src/Replay" + ["Aoc"]="src/Aoc" + ["IssuerDirectory"]="src/IssuerDirectory" + ["Telemetry"]="src/Telemetry" + ["Signals"]="src/Signals" + ["Web"]="src/Web" + ["DevPortal"]="src/DevPortal" +) + +# Modules that use Node.js/npm instead of .NET +declare -a NODE_MODULES=("Web" "DevPortal") + +# Detect which modules have changed based on git diff +detect_changed_modules() { + local base_branch="${1:-main}" + local changed_files + changed_files=$(get_changed_files "$base_branch") + + local changed_modules=() + local module + local paths + + for module in "${!MODULE_PATHS[@]}"; do + paths="${MODULE_PATHS[$module]}" + for path in $paths; do + if echo "$changed_files" | grep -q "^${path}/"; then + if ! array_contains "$module" "${changed_modules[@]}"; then + changed_modules+=("$module") + fi + break + fi + done + done + + # Check for infrastructure changes that affect all modules + if echo "$changed_files" | grep -qE "^(Directory\.Build\.props|Directory\.Packages\.props|nuget\.config)"; then + echo "ALL" + return + fi + + # Check for shared library changes + if echo "$changed_files" | grep -q "^src/__Libraries/"; then + echo "ALL" + return + fi + + if [[ ${#changed_modules[@]} -eq 0 ]]; then + echo "NONE" + else + echo "${changed_modules[*]}" + fi +} + +# ============================================================================= +# RESULT REPORTING +# ============================================================================= + +# Print a summary table row +print_table_row() { + local col1="$1" + local col2="$2" + local col3="${3:-}" + + printf " %-30s %-15s %s\n" "$col1" "$col2" "$col3" +} + +# Print pass/fail status +print_status() { + local name="$1" + local passed="$2" + local duration="${3:-}" + + if [[ "$passed" == "true" ]]; then + print_table_row "$name" "${GREEN}PASSED${RESET}" "$duration" + else + print_table_row "$name" "${RED}FAILED${RESET}" "$duration" + fi +} + +# ============================================================================= +# ENVIRONMENT LOADING +# ============================================================================= + +# Load environment file if it exists +load_env_file() { + local env_file="$1" + + if [[ -f "$env_file" ]]; then + log_debug "Loading environment from: $env_file" + set -a + # shellcheck source=/dev/null + source "$env_file" + set +a + return 0 + fi + return 1 +} diff --git a/devops/scripts/lib/ci-docker.sh b/devops/scripts/lib/ci-docker.sh new file mode 100644 index 000000000..f96a6ecae --- /dev/null +++ b/devops/scripts/lib/ci-docker.sh @@ -0,0 +1,342 @@ +#!/usr/bin/env bash +# ============================================================================= +# CI DOCKER UTILITIES +# ============================================================================= +# Docker-related utility functions for local CI testing. +# +# Usage: +# source "$SCRIPT_DIR/lib/ci-docker.sh" +# +# ============================================================================= + +# Prevent multiple sourcing +[[ -n "${_CI_DOCKER_LOADED:-}" ]] && return +_CI_DOCKER_LOADED=1 + +# ============================================================================= +# CONFIGURATION +# ============================================================================= + +CI_COMPOSE_FILE="${CI_COMPOSE_FILE:-devops/compose/docker-compose.ci.yaml}" +CI_IMAGE="${CI_IMAGE:-stellaops-ci:local}" +CI_DOCKERFILE="${CI_DOCKERFILE:-devops/docker/Dockerfile.ci}" +CI_PROJECT_NAME="${CI_PROJECT_NAME:-stellaops-ci}" + +# Service names from docker-compose.ci.yaml +CI_SERVICES=(postgres-ci valkey-ci nats-ci mock-registry minio-ci) + +# ============================================================================= +# DOCKER CHECK +# ============================================================================= + +# Check if Docker is available and running +check_docker() { + if ! command -v docker &>/dev/null; then + log_error "Docker is not installed or not in PATH" + log_info "Install Docker: https://docs.docker.com/get-docker/" + return 1 + fi + + if ! docker info &>/dev/null; then + log_error "Docker daemon is not running" + log_info "Start Docker Desktop or run: sudo systemctl start docker" + return 1 + fi + + log_debug "Docker is available and running" + return 0 +} + +# Check if Docker Compose is available +check_docker_compose() { + if docker compose version &>/dev/null; then + DOCKER_COMPOSE="docker compose" + log_debug "Using Docker Compose plugin" + return 0 + elif command -v docker-compose &>/dev/null; then + DOCKER_COMPOSE="docker-compose" + log_debug "Using standalone docker-compose" + return 0 + else + log_error "Docker Compose is not installed" + log_info "Install with: docker compose plugin or standalone docker-compose" + return 1 + fi +} + +# ============================================================================= +# CI SERVICES MANAGEMENT +# ============================================================================= + +# Start CI services +start_ci_services() { + local services=("$@") + local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE" + + if [[ ! -f "$compose_file" ]]; then + log_error "Compose file not found: $compose_file" + return 1 + fi + + check_docker || return 1 + check_docker_compose || return 1 + + log_section "Starting CI Services" + + if [[ ${#services[@]} -eq 0 ]]; then + # Start all services + log_info "Starting all CI services..." + $DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" up -d + else + # Start specific services + log_info "Starting services: ${services[*]}" + $DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" up -d "${services[@]}" + fi + + local result=$? + if [[ $result -ne 0 ]]; then + log_error "Failed to start CI services" + return $result + fi + + # Wait for services to be healthy + wait_for_services "${services[@]}" +} + +# Stop CI services +stop_ci_services() { + local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE" + + if [[ ! -f "$compose_file" ]]; then + log_debug "Compose file not found, nothing to stop" + return 0 + fi + + check_docker_compose || return 1 + + log_section "Stopping CI Services" + + $DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" down +} + +# Stop CI services and remove volumes +cleanup_ci_services() { + local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE" + + if [[ ! -f "$compose_file" ]]; then + return 0 + fi + + check_docker_compose || return 1 + + log_section "Cleaning Up CI Services" + + $DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" down -v --remove-orphans +} + +# Check status of CI services +check_ci_services_status() { + local compose_file="$REPO_ROOT/$CI_COMPOSE_FILE" + + check_docker_compose || return 1 + + log_subsection "CI Services Status" + $DOCKER_COMPOSE -f "$compose_file" -p "$CI_PROJECT_NAME" ps +} + +# ============================================================================= +# HEALTH CHECKS +# ============================================================================= + +# Wait for a specific service to be healthy +wait_for_service() { + local service="$1" + local timeout="${2:-60}" + local interval="${3:-2}" + + log_info "Waiting for $service to be healthy..." + + local elapsed=0 + while [[ $elapsed -lt $timeout ]]; do + local status + status=$(docker inspect --format='{{.State.Health.Status}}' "${CI_PROJECT_NAME}-${service}-1" 2>/dev/null || echo "not found") + + if [[ "$status" == "healthy" ]]; then + log_success "$service is healthy" + return 0 + elif [[ "$status" == "not found" ]]; then + # Container might not have health check, check if running + local running + running=$(docker inspect --format='{{.State.Running}}' "${CI_PROJECT_NAME}-${service}-1" 2>/dev/null || echo "false") + if [[ "$running" == "true" ]]; then + log_success "$service is running (no health check)" + return 0 + fi + fi + + sleep "$interval" + elapsed=$((elapsed + interval)) + done + + log_error "$service did not become healthy within ${timeout}s" + return 1 +} + +# Wait for multiple services to be healthy +wait_for_services() { + local services=("$@") + local failed=0 + + if [[ ${#services[@]} -eq 0 ]]; then + services=("${CI_SERVICES[@]}") + fi + + log_info "Waiting for services to be ready..." + + for service in "${services[@]}"; do + if ! wait_for_service "$service" 60 2; then + failed=1 + fi + done + + return $failed +} + +# Check if PostgreSQL is accepting connections +check_postgres_ready() { + local host="${1:-localhost}" + local port="${2:-5433}" + local user="${3:-stellaops_ci}" + local db="${4:-stellaops_test}" + + if command -v pg_isready &>/dev/null; then + pg_isready -h "$host" -p "$port" -U "$user" -d "$db" &>/dev/null + else + # Fallback to nc if pg_isready not available + nc -z "$host" "$port" &>/dev/null + fi +} + +# Check if Valkey/Redis is accepting connections +check_valkey_ready() { + local host="${1:-localhost}" + local port="${2:-6380}" + + if command -v valkey-cli &>/dev/null; then + valkey-cli -h "$host" -p "$port" ping &>/dev/null + elif command -v redis-cli &>/dev/null; then + redis-cli -h "$host" -p "$port" ping &>/dev/null + else + nc -z "$host" "$port" &>/dev/null + fi +} + +# ============================================================================= +# CI DOCKER IMAGE MANAGEMENT +# ============================================================================= + +# Check if CI image exists +ci_image_exists() { + docker image inspect "$CI_IMAGE" &>/dev/null +} + +# Build CI Docker image +build_ci_image() { + local force_rebuild="${1:-false}" + local dockerfile="$REPO_ROOT/$CI_DOCKERFILE" + + if [[ ! -f "$dockerfile" ]]; then + log_error "Dockerfile not found: $dockerfile" + return 1 + fi + + check_docker || return 1 + + if ci_image_exists && [[ "$force_rebuild" != "true" ]]; then + log_info "CI image already exists: $CI_IMAGE" + log_info "Use --rebuild to force rebuild" + return 0 + fi + + log_section "Building CI Docker Image" + log_info "Dockerfile: $dockerfile" + log_info "Image: $CI_IMAGE" + + docker build -t "$CI_IMAGE" -f "$dockerfile" "$REPO_ROOT" + + if [[ $? -ne 0 ]]; then + log_error "Failed to build CI image" + return 1 + fi + + log_success "CI image built successfully: $CI_IMAGE" +} + +# ============================================================================= +# CONTAINER EXECUTION +# ============================================================================= + +# Run a command inside the CI container +run_in_ci_container() { + local command="$*" + + check_docker || return 1 + + if ! ci_image_exists; then + log_info "CI image not found, building..." + build_ci_image || return 1 + fi + + local docker_args=( + --rm + -v "$REPO_ROOT:/src" + -v "$REPO_ROOT/TestResults:/src/TestResults" + -e DOTNET_NOLOGO=1 + -e DOTNET_CLI_TELEMETRY_OPTOUT=1 + -e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 + -e TZ=UTC + -w /src + ) + + # Mount Docker socket for Testcontainers + if [[ -S /var/run/docker.sock ]]; then + docker_args+=(-v /var/run/docker.sock:/var/run/docker.sock) + fi + + # Load environment file if exists + local env_file="$REPO_ROOT/devops/ci-local/.env.local" + if [[ -f "$env_file" ]]; then + docker_args+=(--env-file "$env_file") + fi + + # Connect to CI network if services are running + if docker network inspect stellaops-ci-net &>/dev/null; then + docker_args+=(--network stellaops-ci-net) + fi + + log_debug "Running in CI container: $command" + docker run "${docker_args[@]}" "$CI_IMAGE" bash -c "$command" +} + +# ============================================================================= +# DOCKER NETWORK UTILITIES +# ============================================================================= + +# Get the IP address of a running container +get_container_ip() { + local container="$1" + docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container" 2>/dev/null +} + +# Check if container is running +is_container_running() { + local container="$1" + [[ "$(docker inspect -f '{{.State.Running}}' "$container" 2>/dev/null)" == "true" ]] +} + +# Get container logs +get_container_logs() { + local container="$1" + local lines="${2:-100}" + docker logs --tail "$lines" "$container" 2>&1 +} diff --git a/devops/scripts/lib/ci-web.sh b/devops/scripts/lib/ci-web.sh new file mode 100644 index 000000000..a96c1409c --- /dev/null +++ b/devops/scripts/lib/ci-web.sh @@ -0,0 +1,475 @@ +#!/usr/bin/env bash +# ============================================================================= +# CI-WEB.SH - Angular Web Testing Utilities +# ============================================================================= +# Functions for running Angular/Web frontend tests locally. +# +# Test Types: +# - Unit Tests (Karma/Jasmine) +# - E2E Tests (Playwright) +# - Accessibility Tests (Axe-core) +# - Lighthouse Audits +# - Storybook Build +# +# ============================================================================= + +# Prevent direct execution +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + echo "This script should be sourced, not executed directly." + exit 1 +fi + +# ============================================================================= +# CONSTANTS +# ============================================================================= + +WEB_DIR="${REPO_ROOT:-$(git rev-parse --show-toplevel)}/src/Web/StellaOps.Web" +WEB_NODE_VERSION="20" + +# Test categories for Web +WEB_TEST_CATEGORIES=( + "web:unit" # Karma unit tests + "web:e2e" # Playwright E2E + "web:a11y" # Accessibility + "web:lighthouse" # Performance/a11y audit + "web:build" # Production build + "web:storybook" # Storybook build +) + +# ============================================================================= +# DEPENDENCY CHECKS +# ============================================================================= + +check_node_version() { + if ! command -v node &>/dev/null; then + log_error "Node.js not found" + log_info "Install Node.js $WEB_NODE_VERSION+: https://nodejs.org" + return 1 + fi + + local version + version=$(node --version | sed 's/v//' | cut -d. -f1) + if [[ "$version" -lt "$WEB_NODE_VERSION" ]]; then + log_warn "Node.js version $version is below recommended $WEB_NODE_VERSION" + else + log_debug "Node.js version: $(node --version)" + fi + return 0 +} + +check_npm() { + if ! command -v npm &>/dev/null; then + log_error "npm not found" + return 1 + fi + log_debug "npm version: $(npm --version)" + return 0 +} + +check_web_dependencies() { + log_subsection "Checking Web Dependencies" + + check_node_version || return 1 + check_npm || return 1 + + # Check if node_modules exists + if [[ ! -d "$WEB_DIR/node_modules" ]]; then + log_warn "node_modules not found - will install dependencies" + fi + + return 0 +} + +# ============================================================================= +# SETUP +# ============================================================================= + +install_web_dependencies() { + log_subsection "Installing Web Dependencies" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + pushd "$WEB_DIR" > /dev/null || return 1 + + # Check if package-lock.json exists + if [[ -f "package-lock.json" ]]; then + log_info "Running npm ci (clean install)..." + npm ci --prefer-offline --no-audit --no-fund || { + log_error "npm ci failed" + popd > /dev/null + return 1 + } + else + log_info "Running npm install..." + npm install --no-audit --no-fund || { + log_error "npm install failed" + popd > /dev/null + return 1 + } + fi + + popd > /dev/null + log_success "Web dependencies installed" + return 0 +} + +ensure_web_dependencies() { + if [[ ! -d "$WEB_DIR/node_modules" ]]; then + install_web_dependencies || return 1 + fi + return 0 +} + +# ============================================================================= +# TEST RUNNERS +# ============================================================================= + +run_web_unit_tests() { + log_subsection "Running Web Unit Tests (Karma/Jasmine)" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + ensure_web_dependencies || return 1 + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: npm run test:ci" + popd > /dev/null + return 0 + fi + + # Run tests + npm run test:ci + local result=$? + + stop_timer "$start_time" "Web unit tests" + popd > /dev/null + + if [[ $result -eq 0 ]]; then + log_success "Web unit tests passed" + else + log_error "Web unit tests failed" + fi + + return $result +} + +run_web_e2e_tests() { + log_subsection "Running Web E2E Tests (Playwright)" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + ensure_web_dependencies || return 1 + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + # Install Playwright browsers if needed + if [[ ! -d "$HOME/.cache/ms-playwright" ]] && [[ ! -d "node_modules/.cache/ms-playwright" ]]; then + log_info "Installing Playwright browsers..." + npx playwright install --with-deps chromium || { + log_warn "Playwright browser installation failed - E2E tests may fail" + } + fi + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: npm run test:e2e" + popd > /dev/null + return 0 + fi + + # Run E2E tests + npm run test:e2e + local result=$? + + stop_timer "$start_time" "Web E2E tests" + popd > /dev/null + + if [[ $result -eq 0 ]]; then + log_success "Web E2E tests passed" + else + log_error "Web E2E tests failed" + fi + + return $result +} + +run_web_a11y_tests() { + log_subsection "Running Web Accessibility Tests (Axe)" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + ensure_web_dependencies || return 1 + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: npm run test:a11y" + popd > /dev/null + return 0 + fi + + # Run accessibility tests + npm run test:a11y + local result=$? + + stop_timer "$start_time" "Web accessibility tests" + popd > /dev/null + + if [[ $result -eq 0 ]]; then + log_success "Web accessibility tests passed" + else + log_warn "Web accessibility tests had issues (non-blocking)" + fi + + # A11y tests are non-blocking by default + return 0 +} + +run_web_build() { + log_subsection "Building Web Application" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + ensure_web_dependencies || return 1 + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: npm run build -- --configuration production" + popd > /dev/null + return 0 + fi + + # Build production bundle + npm run build -- --configuration production --progress=false + local result=$? + + stop_timer "$start_time" "Web build" + popd > /dev/null + + if [[ $result -eq 0 ]]; then + log_success "Web build completed" + + # Check bundle size + if [[ -d "$WEB_DIR/dist" ]]; then + local size + size=$(du -sh "$WEB_DIR/dist" 2>/dev/null | cut -f1) + log_info "Bundle size: $size" + fi + else + log_error "Web build failed" + fi + + return $result +} + +run_web_storybook_build() { + log_subsection "Building Storybook" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + ensure_web_dependencies || return 1 + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: npm run storybook:build" + popd > /dev/null + return 0 + fi + + # Build Storybook + npm run storybook:build + local result=$? + + stop_timer "$start_time" "Storybook build" + popd > /dev/null + + if [[ $result -eq 0 ]]; then + log_success "Storybook build completed" + else + log_error "Storybook build failed" + fi + + return $result +} + +run_web_lighthouse() { + log_subsection "Running Lighthouse Audit" + + if [[ ! -d "$WEB_DIR" ]]; then + log_error "Web directory not found: $WEB_DIR" + return 1 + fi + + # Check if lighthouse is available + if ! command -v lhci &>/dev/null && ! npx lhci --version &>/dev/null 2>&1; then + log_warn "Lighthouse CI not installed - skipping audit" + log_info "Install with: npm install -g @lhci/cli" + return 0 + fi + + ensure_web_dependencies || return 1 + + # Build first if not already built + if [[ ! -d "$WEB_DIR/dist" ]]; then + run_web_build || return 1 + fi + + pushd "$WEB_DIR" > /dev/null || return 1 + + local start_time + start_time=$(start_timer) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would run: lhci autorun" + popd > /dev/null + return 0 + fi + + # Run Lighthouse + npx lhci autorun \ + --collect.staticDistDir=./dist/stellaops-web/browser \ + --collect.numberOfRuns=1 \ + --upload.target=filesystem \ + --upload.outputDir=./lighthouse-results 2>/dev/null || { + log_warn "Lighthouse audit had issues" + } + + stop_timer "$start_time" "Lighthouse audit" + popd > /dev/null + + log_success "Lighthouse audit completed" + return 0 +} + +# ============================================================================= +# COMPOSITE RUNNERS +# ============================================================================= + +run_web_smoke() { + log_section "Web Smoke Tests" + log_info "Running quick web validation" + + local failed=0 + + run_web_build || failed=1 + + if [[ $failed -eq 0 ]]; then + run_web_unit_tests || failed=1 + fi + + return $failed +} + +run_web_pr_gating() { + log_section "Web PR-Gating Tests" + log_info "Running full web PR-gating suite" + + local failed=0 + local results=() + + # Build + run_web_build + results+=("Build:$?") + [[ ${results[-1]##*:} -ne 0 ]] && failed=1 + + # Unit tests + if [[ $failed -eq 0 ]]; then + run_web_unit_tests + results+=("Unit:$?") + [[ ${results[-1]##*:} -ne 0 ]] && failed=1 + fi + + # E2E tests + if [[ $failed -eq 0 ]]; then + run_web_e2e_tests + results+=("E2E:$?") + [[ ${results[-1]##*:} -ne 0 ]] && failed=1 + fi + + # A11y tests (non-blocking) + run_web_a11y_tests + results+=("A11y:$?") + + # Print summary + log_section "Web Test Results" + for result in "${results[@]}"; do + local name="${result%%:*}" + local status="${result##*:}" + if [[ "$status" == "0" ]]; then + print_status "Web $name" "true" + else + print_status "Web $name" "false" + fi + done + + return $failed +} + +run_web_full() { + log_section "Full Web Test Suite" + log_info "Running all web tests including extended categories" + + local failed=0 + + # PR-gating tests + run_web_pr_gating || failed=1 + + # Extended tests + run_web_storybook_build || log_warn "Storybook build failed (non-blocking)" + run_web_lighthouse || log_warn "Lighthouse audit failed (non-blocking)" + + return $failed +} + +# ============================================================================= +# EXPORTS +# ============================================================================= + +export -f check_web_dependencies +export -f install_web_dependencies +export -f ensure_web_dependencies +export -f run_web_unit_tests +export -f run_web_e2e_tests +export -f run_web_a11y_tests +export -f run_web_build +export -f run_web_storybook_build +export -f run_web_lighthouse +export -f run_web_smoke +export -f run_web_pr_gating +export -f run_web_full diff --git a/devops/scripts/lib/exit-codes.sh b/devops/scripts/lib/exit-codes.sh new file mode 100644 index 000000000..20cbd5d58 --- /dev/null +++ b/devops/scripts/lib/exit-codes.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# Shared Exit Codes Registry +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Standard exit codes for all CI/CD scripts +# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/exit-codes.sh" +# +# Exit codes follow POSIX conventions (0-125) +# 126-127 reserved for shell errors +# 128+ reserved for signal handling + +# Prevent multiple sourcing +if [[ -n "${__STELLAOPS_EXIT_CODES_LOADED:-}" ]]; then + return 0 +fi +export __STELLAOPS_EXIT_CODES_LOADED=1 + +# ============================================================================ +# Standard Exit Codes +# ============================================================================ + +# Success +export EXIT_SUCCESS=0 + +# General errors (1-9) +export EXIT_ERROR=1 # Generic error +export EXIT_USAGE=2 # Invalid usage/arguments +export EXIT_CONFIG_ERROR=3 # Configuration error +export EXIT_NOT_FOUND=4 # File/resource not found +export EXIT_PERMISSION=5 # Permission denied +export EXIT_IO_ERROR=6 # I/O error +export EXIT_NETWORK_ERROR=7 # Network error +export EXIT_TIMEOUT=8 # Operation timed out +export EXIT_INTERRUPTED=9 # User interrupted (Ctrl+C) + +# Tool/dependency errors (10-19) +export EXIT_MISSING_TOOL=10 # Required tool not installed +export EXIT_TOOL_ERROR=11 # Tool execution failed +export EXIT_VERSION_MISMATCH=12 # Wrong tool version +export EXIT_DEPENDENCY_ERROR=13 # Dependency resolution failed + +# Build errors (20-29) +export EXIT_BUILD_FAILED=20 # Build compilation failed +export EXIT_RESTORE_FAILED=21 # Package restore failed +export EXIT_PUBLISH_FAILED=22 # Publish failed +export EXIT_PACKAGING_FAILED=23 # Packaging failed + +# Test errors (30-39) +export EXIT_TEST_FAILED=30 # Tests failed +export EXIT_TEST_TIMEOUT=31 # Test timed out +export EXIT_FIXTURE_ERROR=32 # Test fixture error +export EXIT_DETERMINISM_FAIL=33 # Determinism check failed + +# Deployment errors (40-49) +export EXIT_DEPLOY_FAILED=40 # Deployment failed +export EXIT_ROLLBACK_FAILED=41 # Rollback failed +export EXIT_HEALTH_CHECK_FAIL=42 # Health check failed +export EXIT_REGISTRY_ERROR=43 # Container registry error + +# Validation errors (50-59) +export EXIT_VALIDATION_FAILED=50 # General validation failed +export EXIT_SCHEMA_ERROR=51 # Schema validation failed +export EXIT_LINT_ERROR=52 # Lint check failed +export EXIT_FORMAT_ERROR=53 # Format check failed +export EXIT_LICENSE_ERROR=54 # License compliance failed + +# Security errors (60-69) +export EXIT_SECURITY_ERROR=60 # Security check failed +export EXIT_SECRETS_FOUND=61 # Secrets detected in code +export EXIT_VULN_FOUND=62 # Vulnerabilities found +export EXIT_SIGN_FAILED=63 # Signing failed +export EXIT_VERIFY_FAILED=64 # Verification failed + +# Git/VCS errors (70-79) +export EXIT_GIT_ERROR=70 # Git operation failed +export EXIT_DIRTY_WORKTREE=71 # Uncommitted changes +export EXIT_MERGE_CONFLICT=72 # Merge conflict +export EXIT_BRANCH_ERROR=73 # Branch operation failed + +# Reserved for specific tools (80-99) +export EXIT_DOTNET_ERROR=80 # .NET specific error +export EXIT_DOCKER_ERROR=81 # Docker specific error +export EXIT_HELM_ERROR=82 # Helm specific error +export EXIT_KUBECTL_ERROR=83 # kubectl specific error +export EXIT_NPM_ERROR=84 # npm specific error +export EXIT_PYTHON_ERROR=85 # Python specific error + +# Legacy compatibility +export EXIT_TOOLCHAIN=69 # Tool not found (legacy, use EXIT_MISSING_TOOL) + +# ============================================================================ +# Helper Functions +# ============================================================================ + +# Get exit code name from number +exit_code_name() { + local code="${1:-}" + + case "$code" in + 0) echo "SUCCESS" ;; + 1) echo "ERROR" ;; + 2) echo "USAGE" ;; + 3) echo "CONFIG_ERROR" ;; + 4) echo "NOT_FOUND" ;; + 5) echo "PERMISSION" ;; + 6) echo "IO_ERROR" ;; + 7) echo "NETWORK_ERROR" ;; + 8) echo "TIMEOUT" ;; + 9) echo "INTERRUPTED" ;; + 10) echo "MISSING_TOOL" ;; + 11) echo "TOOL_ERROR" ;; + 12) echo "VERSION_MISMATCH" ;; + 13) echo "DEPENDENCY_ERROR" ;; + 20) echo "BUILD_FAILED" ;; + 21) echo "RESTORE_FAILED" ;; + 22) echo "PUBLISH_FAILED" ;; + 23) echo "PACKAGING_FAILED" ;; + 30) echo "TEST_FAILED" ;; + 31) echo "TEST_TIMEOUT" ;; + 32) echo "FIXTURE_ERROR" ;; + 33) echo "DETERMINISM_FAIL" ;; + 40) echo "DEPLOY_FAILED" ;; + 41) echo "ROLLBACK_FAILED" ;; + 42) echo "HEALTH_CHECK_FAIL" ;; + 43) echo "REGISTRY_ERROR" ;; + 50) echo "VALIDATION_FAILED" ;; + 51) echo "SCHEMA_ERROR" ;; + 52) echo "LINT_ERROR" ;; + 53) echo "FORMAT_ERROR" ;; + 54) echo "LICENSE_ERROR" ;; + 60) echo "SECURITY_ERROR" ;; + 61) echo "SECRETS_FOUND" ;; + 62) echo "VULN_FOUND" ;; + 63) echo "SIGN_FAILED" ;; + 64) echo "VERIFY_FAILED" ;; + 69) echo "TOOLCHAIN (legacy)" ;; + 70) echo "GIT_ERROR" ;; + 71) echo "DIRTY_WORKTREE" ;; + 72) echo "MERGE_CONFLICT" ;; + 73) echo "BRANCH_ERROR" ;; + 80) echo "DOTNET_ERROR" ;; + 81) echo "DOCKER_ERROR" ;; + 82) echo "HELM_ERROR" ;; + 83) echo "KUBECTL_ERROR" ;; + 84) echo "NPM_ERROR" ;; + 85) echo "PYTHON_ERROR" ;; + 126) echo "COMMAND_NOT_EXECUTABLE" ;; + 127) echo "COMMAND_NOT_FOUND" ;; + *) + if [[ $code -ge 128 ]] && [[ $code -le 255 ]]; then + local signal=$((code - 128)) + echo "SIGNAL_${signal}" + else + echo "UNKNOWN_${code}" + fi + ;; + esac +} + +# Check if exit code indicates success +is_success() { + [[ "${1:-1}" -eq 0 ]] +} + +# Check if exit code indicates error +is_error() { + [[ "${1:-0}" -ne 0 ]] +} + +# Exit with message and code +exit_with() { + local code="${1:-1}" + shift + if [[ $# -gt 0 ]]; then + echo "$@" >&2 + fi + exit "$code" +} diff --git a/devops/scripts/lib/git-utils.sh b/devops/scripts/lib/git-utils.sh new file mode 100644 index 000000000..4a2249d03 --- /dev/null +++ b/devops/scripts/lib/git-utils.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env bash +# Shared Git Utilities +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Common git operations for CI/CD scripts +# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/git-utils.sh" + +# Prevent multiple sourcing +if [[ -n "${__STELLAOPS_GIT_UTILS_LOADED:-}" ]]; then + return 0 +fi +export __STELLAOPS_GIT_UTILS_LOADED=1 + +# Source dependencies +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/logging.sh" 2>/dev/null || true +source "${SCRIPT_DIR}/exit-codes.sh" 2>/dev/null || true + +# ============================================================================ +# Repository Information +# ============================================================================ + +# Get repository root directory +git_root() { + git rev-parse --show-toplevel 2>/dev/null || echo "." +} + +# Check if current directory is a git repository +is_git_repo() { + git rev-parse --git-dir >/dev/null 2>&1 +} + +# Get current commit SHA (full) +git_sha() { + git rev-parse HEAD 2>/dev/null +} + +# Get current commit SHA (short) +git_sha_short() { + git rev-parse --short HEAD 2>/dev/null +} + +# Get current branch name +git_branch() { + git rev-parse --abbrev-ref HEAD 2>/dev/null +} + +# Get current tag (if HEAD is tagged) +git_tag() { + git describe --tags --exact-match HEAD 2>/dev/null || echo "" +} + +# Get latest tag +git_latest_tag() { + git describe --tags --abbrev=0 2>/dev/null || echo "" +} + +# Get remote URL +git_remote_url() { + local remote="${1:-origin}" + git remote get-url "$remote" 2>/dev/null +} + +# Get repository name from remote URL +git_repo_name() { + local url + url=$(git_remote_url "${1:-origin}") + basename "$url" .git +} + +# ============================================================================ +# Commit Information +# ============================================================================ + +# Get commit message +git_commit_message() { + local sha="${1:-HEAD}" + git log -1 --format="%s" "$sha" 2>/dev/null +} + +# Get commit author +git_commit_author() { + local sha="${1:-HEAD}" + git log -1 --format="%an" "$sha" 2>/dev/null +} + +# Get commit author email +git_commit_author_email() { + local sha="${1:-HEAD}" + git log -1 --format="%ae" "$sha" 2>/dev/null +} + +# Get commit timestamp (ISO 8601) +git_commit_timestamp() { + local sha="${1:-HEAD}" + git log -1 --format="%aI" "$sha" 2>/dev/null +} + +# Get commit timestamp (Unix epoch) +git_commit_epoch() { + local sha="${1:-HEAD}" + git log -1 --format="%at" "$sha" 2>/dev/null +} + +# ============================================================================ +# Working Tree State +# ============================================================================ + +# Check if working tree is clean +git_is_clean() { + [[ -z "$(git status --porcelain 2>/dev/null)" ]] +} + +# Check if working tree is dirty +git_is_dirty() { + ! git_is_clean +} + +# Get list of changed files +git_changed_files() { + git status --porcelain 2>/dev/null | awk '{print $2}' +} + +# Get list of staged files +git_staged_files() { + git diff --cached --name-only 2>/dev/null +} + +# Get list of untracked files +git_untracked_files() { + git ls-files --others --exclude-standard 2>/dev/null +} + +# ============================================================================ +# Diff and History +# ============================================================================ + +# Get files changed between two refs +git_diff_files() { + local from="${1:-HEAD~1}" + local to="${2:-HEAD}" + git diff --name-only "$from" "$to" 2>/dev/null +} + +# Get files changed in last N commits +git_recent_files() { + local count="${1:-1}" + git diff --name-only "HEAD~${count}" HEAD 2>/dev/null +} + +# Check if file was changed between two refs +git_file_changed() { + local file="$1" + local from="${2:-HEAD~1}" + local to="${3:-HEAD}" + git diff --name-only "$from" "$to" -- "$file" 2>/dev/null | grep -q "$file" +} + +# Get commits between two refs +git_commits_between() { + local from="${1:-HEAD~10}" + local to="${2:-HEAD}" + git log --oneline "$from".."$to" 2>/dev/null +} + +# ============================================================================ +# Tag Operations +# ============================================================================ + +# Create a tag +git_create_tag() { + local tag="$1" + local message="${2:-}" + + if [[ -n "$message" ]]; then + git tag -a "$tag" -m "$message" + else + git tag "$tag" + fi +} + +# Delete a tag +git_delete_tag() { + local tag="$1" + git tag -d "$tag" 2>/dev/null +} + +# Push tag to remote +git_push_tag() { + local tag="$1" + local remote="${2:-origin}" + git push "$remote" "$tag" +} + +# List tags matching pattern +git_list_tags() { + local pattern="${1:-*}" + git tag -l "$pattern" 2>/dev/null +} + +# ============================================================================ +# Branch Operations +# ============================================================================ + +# Check if branch exists +git_branch_exists() { + local branch="$1" + git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null +} + +# Check if remote branch exists +git_remote_branch_exists() { + local branch="$1" + local remote="${2:-origin}" + git show-ref --verify --quiet "refs/remotes/$remote/$branch" 2>/dev/null +} + +# Get default branch +git_default_branch() { + local remote="${1:-origin}" + git remote show "$remote" 2>/dev/null | grep "HEAD branch" | awk '{print $NF}' +} + +# ============================================================================ +# CI/CD Helpers +# ============================================================================ + +# Get version string for CI builds +git_ci_version() { + local tag + tag=$(git_tag) + + if [[ -n "$tag" ]]; then + echo "$tag" + else + local branch sha + branch=$(git_branch | tr '/' '-') + sha=$(git_sha_short) + echo "${branch}-${sha}" + fi +} + +# Check if current commit is on default branch +git_is_default_branch() { + local current default + current=$(git_branch) + default=$(git_default_branch) + [[ "$current" == "$default" ]] +} + +# Check if running in CI environment +git_is_ci() { + [[ -n "${CI:-}" ]] || [[ -n "${GITHUB_ACTIONS:-}" ]] || [[ -n "${GITLAB_CI:-}" ]] +} + +# Ensure clean worktree or fail +git_require_clean() { + if git_is_dirty; then + log_error "Working tree is dirty. Commit or stash changes first." + return "${EXIT_DIRTY_WORKTREE:-71}" + fi +} diff --git a/devops/scripts/lib/hash-utils.sh b/devops/scripts/lib/hash-utils.sh new file mode 100644 index 000000000..ade90039b --- /dev/null +++ b/devops/scripts/lib/hash-utils.sh @@ -0,0 +1,266 @@ +#!/usr/bin/env bash +# Shared Hash/Checksum Utilities +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Cryptographic hash and checksum operations for CI/CD scripts +# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/hash-utils.sh" + +# Prevent multiple sourcing +if [[ -n "${__STELLAOPS_HASH_UTILS_LOADED:-}" ]]; then + return 0 +fi +export __STELLAOPS_HASH_UTILS_LOADED=1 + +# Source dependencies +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/logging.sh" 2>/dev/null || true +source "${SCRIPT_DIR}/exit-codes.sh" 2>/dev/null || true + +# ============================================================================ +# Hash Computation +# ============================================================================ + +# Compute SHA-256 hash of a file +compute_sha256() { + local file="$1" + + if [[ ! -f "$file" ]]; then + log_error "File not found: $file" + return "${EXIT_NOT_FOUND:-4}" + fi + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 "$file" | awk '{print $NF}' + else + log_error "No SHA-256 tool available" + return "${EXIT_MISSING_TOOL:-10}" + fi +} + +# Compute SHA-512 hash of a file +compute_sha512() { + local file="$1" + + if [[ ! -f "$file" ]]; then + log_error "File not found: $file" + return "${EXIT_NOT_FOUND:-4}" + fi + + if command -v sha512sum >/dev/null 2>&1; then + sha512sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 512 "$file" | awk '{print $1}' + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -sha512 "$file" | awk '{print $NF}' + else + log_error "No SHA-512 tool available" + return "${EXIT_MISSING_TOOL:-10}" + fi +} + +# Compute MD5 hash of a file (for compatibility, not security) +compute_md5() { + local file="$1" + + if [[ ! -f "$file" ]]; then + log_error "File not found: $file" + return "${EXIT_NOT_FOUND:-4}" + fi + + if command -v md5sum >/dev/null 2>&1; then + md5sum "$file" | awk '{print $1}' + elif command -v md5 >/dev/null 2>&1; then + md5 -q "$file" + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -md5 "$file" | awk '{print $NF}' + else + log_error "No MD5 tool available" + return "${EXIT_MISSING_TOOL:-10}" + fi +} + +# Compute hash of string +compute_string_hash() { + local string="$1" + local algorithm="${2:-sha256}" + + case "$algorithm" in + sha256) + echo -n "$string" | sha256sum 2>/dev/null | awk '{print $1}' || \ + echo -n "$string" | shasum -a 256 2>/dev/null | awk '{print $1}' + ;; + sha512) + echo -n "$string" | sha512sum 2>/dev/null | awk '{print $1}' || \ + echo -n "$string" | shasum -a 512 2>/dev/null | awk '{print $1}' + ;; + md5) + echo -n "$string" | md5sum 2>/dev/null | awk '{print $1}' || \ + echo -n "$string" | md5 2>/dev/null + ;; + *) + log_error "Unknown algorithm: $algorithm" + return "${EXIT_USAGE:-2}" + ;; + esac +} + +# ============================================================================ +# Checksum Files +# ============================================================================ + +# Write checksum file for a single file +write_checksum() { + local file="$1" + local checksum_file="${2:-${file}.sha256}" + local algorithm="${3:-sha256}" + + local hash + case "$algorithm" in + sha256) hash=$(compute_sha256 "$file") ;; + sha512) hash=$(compute_sha512 "$file") ;; + md5) hash=$(compute_md5 "$file") ;; + *) + log_error "Unknown algorithm: $algorithm" + return "${EXIT_USAGE:-2}" + ;; + esac + + if [[ -z "$hash" ]]; then + return "${EXIT_ERROR:-1}" + fi + + local basename + basename=$(basename "$file") + echo "$hash $basename" > "$checksum_file" + log_debug "Wrote checksum to $checksum_file" +} + +# Write checksums for multiple files +write_checksums() { + local output_file="$1" + shift + local files=("$@") + + : > "$output_file" + + for file in "${files[@]}"; do + if [[ -f "$file" ]]; then + local hash basename + hash=$(compute_sha256 "$file") + basename=$(basename "$file") + echo "$hash $basename" >> "$output_file" + fi + done + + log_debug "Wrote checksums to $output_file" +} + +# ============================================================================ +# Checksum Verification +# ============================================================================ + +# Verify checksum of a file +verify_checksum() { + local file="$1" + local expected_hash="$2" + local algorithm="${3:-sha256}" + + local actual_hash + case "$algorithm" in + sha256) actual_hash=$(compute_sha256 "$file") ;; + sha512) actual_hash=$(compute_sha512 "$file") ;; + md5) actual_hash=$(compute_md5 "$file") ;; + *) + log_error "Unknown algorithm: $algorithm" + return "${EXIT_USAGE:-2}" + ;; + esac + + if [[ "$actual_hash" == "$expected_hash" ]]; then + log_debug "Checksum verified: $file" + return 0 + else + log_error "Checksum mismatch for $file" + log_error " Expected: $expected_hash" + log_error " Actual: $actual_hash" + return "${EXIT_VERIFY_FAILED:-64}" + fi +} + +# Verify checksums from file (sha256sum -c style) +verify_checksums_file() { + local checksum_file="$1" + local base_dir="${2:-.}" + + if [[ ! -f "$checksum_file" ]]; then + log_error "Checksum file not found: $checksum_file" + return "${EXIT_NOT_FOUND:-4}" + fi + + local failures=0 + + while IFS= read -r line; do + # Skip empty lines and comments + [[ -z "$line" ]] && continue + [[ "$line" == \#* ]] && continue + + local hash filename + hash=$(echo "$line" | awk '{print $1}') + filename=$(echo "$line" | awk '{print $2}') + + local filepath="${base_dir}/${filename}" + + if [[ ! -f "$filepath" ]]; then + log_error "File not found: $filepath" + ((failures++)) + continue + fi + + if ! verify_checksum "$filepath" "$hash"; then + ((failures++)) + fi + done < "$checksum_file" + + if [[ $failures -gt 0 ]]; then + log_error "$failures checksum verification(s) failed" + return "${EXIT_VERIFY_FAILED:-64}" + fi + + log_info "All checksums verified" + return 0 +} + +# ============================================================================ +# Helpers +# ============================================================================ + +# Check if two files have the same content +files_identical() { + local file1="$1" + local file2="$2" + + [[ -f "$file1" ]] && [[ -f "$file2" ]] || return 1 + + local hash1 hash2 + hash1=$(compute_sha256 "$file1") + hash2=$(compute_sha256 "$file2") + + [[ "$hash1" == "$hash2" ]] +} + +# Get short hash for display +short_hash() { + local hash="$1" + local length="${2:-8}" + echo "${hash:0:$length}" +} + +# Generate deterministic ID from inputs +generate_id() { + local inputs="$*" + compute_string_hash "$inputs" sha256 | head -c 16 +} diff --git a/devops/scripts/lib/logging.sh b/devops/scripts/lib/logging.sh new file mode 100644 index 000000000..4e363d6f8 --- /dev/null +++ b/devops/scripts/lib/logging.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# Shared Logging Library +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Standard logging functions for all CI/CD scripts +# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/logging.sh" +# +# Log Levels: DEBUG, INFO, WARN, ERROR +# Set LOG_LEVEL environment variable to control verbosity (default: INFO) + +# Prevent multiple sourcing +if [[ -n "${__STELLAOPS_LOGGING_LOADED:-}" ]]; then + return 0 +fi +export __STELLAOPS_LOGGING_LOADED=1 + +# Colors (disable with NO_COLOR=1) +if [[ -z "${NO_COLOR:-}" ]] && [[ -t 1 ]]; then + export LOG_COLOR_RED='\033[0;31m' + export LOG_COLOR_GREEN='\033[0;32m' + export LOG_COLOR_YELLOW='\033[1;33m' + export LOG_COLOR_BLUE='\033[0;34m' + export LOG_COLOR_MAGENTA='\033[0;35m' + export LOG_COLOR_CYAN='\033[0;36m' + export LOG_COLOR_GRAY='\033[0;90m' + export LOG_COLOR_RESET='\033[0m' +else + export LOG_COLOR_RED='' + export LOG_COLOR_GREEN='' + export LOG_COLOR_YELLOW='' + export LOG_COLOR_BLUE='' + export LOG_COLOR_MAGENTA='' + export LOG_COLOR_CYAN='' + export LOG_COLOR_GRAY='' + export LOG_COLOR_RESET='' +fi + +# Log level configuration +export LOG_LEVEL="${LOG_LEVEL:-INFO}" + +# Convert log level to numeric for comparison +_log_level_to_num() { + case "$1" in + DEBUG) echo 0 ;; + INFO) echo 1 ;; + WARN) echo 2 ;; + ERROR) echo 3 ;; + *) echo 1 ;; + esac +} + +# Check if message should be logged based on level +_should_log() { + local msg_level="$1" + local current_level="${LOG_LEVEL:-INFO}" + + local msg_num current_num + msg_num=$(_log_level_to_num "$msg_level") + current_num=$(_log_level_to_num "$current_level") + + [[ $msg_num -ge $current_num ]] +} + +# Format timestamp +_log_timestamp() { + if [[ "${LOG_TIMESTAMPS:-true}" == "true" ]]; then + date -u +"%Y-%m-%dT%H:%M:%SZ" + fi +} + +# Core logging function +_log() { + local level="$1" + local color="$2" + shift 2 + + if ! _should_log "$level"; then + return 0 + fi + + local timestamp + timestamp=$(_log_timestamp) + + local prefix="" + if [[ -n "$timestamp" ]]; then + prefix="${LOG_COLOR_GRAY}${timestamp}${LOG_COLOR_RESET} " + fi + + echo -e "${prefix}${color}[${level}]${LOG_COLOR_RESET} $*" +} + +# Public logging functions +log_debug() { + _log "DEBUG" "${LOG_COLOR_GRAY}" "$@" +} + +log_info() { + _log "INFO" "${LOG_COLOR_GREEN}" "$@" +} + +log_warn() { + _log "WARN" "${LOG_COLOR_YELLOW}" "$@" +} + +log_error() { + _log "ERROR" "${LOG_COLOR_RED}" "$@" >&2 +} + +# Step logging (for workflow stages) +log_step() { + _log "STEP" "${LOG_COLOR_BLUE}" "$@" +} + +# Success message +log_success() { + _log "OK" "${LOG_COLOR_GREEN}" "$@" +} + +# GitHub Actions annotations +log_gh_notice() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::notice::$*" + else + log_info "$@" + fi +} + +log_gh_warning() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::warning::$*" + else + log_warn "$@" + fi +} + +log_gh_error() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::error::$*" + else + log_error "$@" + fi +} + +# Group logging (for GitHub Actions) +log_group_start() { + local title="$1" + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::group::$title" + else + log_step "=== $title ===" + fi +} + +log_group_end() { + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::endgroup::" + fi +} + +# Masked logging (for secrets) +log_masked() { + local value="$1" + if [[ -n "${GITHUB_ACTIONS:-}" ]]; then + echo "::add-mask::$value" + fi +} + +# Die with error message +die() { + log_error "$@" + exit 1 +} + +# Conditional die +die_if() { + local condition="$1" + shift + if eval "$condition"; then + die "$@" + fi +} diff --git a/devops/scripts/lib/path-utils.sh b/devops/scripts/lib/path-utils.sh new file mode 100644 index 000000000..0298073da --- /dev/null +++ b/devops/scripts/lib/path-utils.sh @@ -0,0 +1,274 @@ +#!/usr/bin/env bash +# Shared Path Utilities +# Sprint: CI/CD Enhancement - Script Consolidation +# +# Purpose: Path manipulation and file operations for CI/CD scripts +# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/path-utils.sh" + +# Prevent multiple sourcing +if [[ -n "${__STELLAOPS_PATH_UTILS_LOADED:-}" ]]; then + return 0 +fi +export __STELLAOPS_PATH_UTILS_LOADED=1 + +# Source dependencies +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/logging.sh" 2>/dev/null || true +source "${SCRIPT_DIR}/exit-codes.sh" 2>/dev/null || true + +# ============================================================================ +# Path Normalization +# ============================================================================ + +# Normalize path (resolve .., ., symlinks) +normalize_path() { + local path="$1" + + # Handle empty path + if [[ -z "$path" ]]; then + echo "." + return 0 + fi + + # Try realpath first (most reliable) + if command -v realpath >/dev/null 2>&1; then + realpath -m "$path" 2>/dev/null && return 0 + fi + + # Fallback to Python + if command -v python3 >/dev/null 2>&1; then + python3 -c "import os; print(os.path.normpath('$path'))" 2>/dev/null && return 0 + fi + + # Manual normalization (basic) + echo "$path" | sed 's|/\./|/|g' | sed 's|/[^/]*/\.\./|/|g' | sed 's|//|/|g' +} + +# Get absolute path +absolute_path() { + local path="$1" + + if [[ "$path" == /* ]]; then + normalize_path "$path" + else + normalize_path "$(pwd)/$path" + fi +} + +# Get relative path from one path to another +relative_path() { + local from="$1" + local to="$2" + + if command -v realpath >/dev/null 2>&1; then + realpath --relative-to="$from" "$to" 2>/dev/null && return 0 + fi + + if command -v python3 >/dev/null 2>&1; then + python3 -c "import os.path; print(os.path.relpath('$to', '$from'))" 2>/dev/null && return 0 + fi + + # Fallback: just return absolute path + absolute_path "$to" +} + +# ============================================================================ +# Path Components +# ============================================================================ + +# Get directory name +dir_name() { + dirname "$1" +} + +# Get base name +base_name() { + basename "$1" +} + +# Get file extension +file_extension() { + local path="$1" + local base + base=$(basename "$path") + + if [[ "$base" == *.* ]]; then + echo "${base##*.}" + else + echo "" + fi +} + +# Get file name without extension +file_stem() { + local path="$1" + local base + base=$(basename "$path") + + if [[ "$base" == *.* ]]; then + echo "${base%.*}" + else + echo "$base" + fi +} + +# ============================================================================ +# Directory Operations +# ============================================================================ + +# Ensure directory exists +ensure_directory() { + local dir="$1" + if [[ ! -d "$dir" ]]; then + mkdir -p "$dir" + fi +} + +# Create temporary directory +create_temp_dir() { + local prefix="${1:-stellaops}" + mktemp -d "${TMPDIR:-/tmp}/${prefix}.XXXXXX" +} + +# Create temporary file +create_temp_file() { + local prefix="${1:-stellaops}" + local suffix="${2:-}" + mktemp "${TMPDIR:-/tmp}/${prefix}.XXXXXX${suffix}" +} + +# Clean temporary directory +clean_temp() { + local path="$1" + if [[ -d "$path" ]] && [[ "$path" == *stellaops* ]]; then + rm -rf "$path" + fi +} + +# ============================================================================ +# File Existence Checks +# ============================================================================ + +# Check if file exists +file_exists() { + [[ -f "$1" ]] +} + +# Check if directory exists +dir_exists() { + [[ -d "$1" ]] +} + +# Check if path exists (file or directory) +path_exists() { + [[ -e "$1" ]] +} + +# Check if file is readable +file_readable() { + [[ -r "$1" ]] +} + +# Check if file is writable +file_writable() { + [[ -w "$1" ]] +} + +# Check if file is executable +file_executable() { + [[ -x "$1" ]] +} + +# ============================================================================ +# File Discovery +# ============================================================================ + +# Find files by pattern +find_files() { + local dir="${1:-.}" + local pattern="${2:-*}" + find "$dir" -type f -name "$pattern" 2>/dev/null +} + +# Find files by extension +find_by_extension() { + local dir="${1:-.}" + local ext="${2:-}" + find "$dir" -type f -name "*.${ext}" 2>/dev/null +} + +# Find project files (csproj, package.json, etc.) +find_project_files() { + local dir="${1:-.}" + find "$dir" -type f \( \ + -name "*.csproj" -o \ + -name "*.fsproj" -o \ + -name "package.json" -o \ + -name "Cargo.toml" -o \ + -name "go.mod" -o \ + -name "pom.xml" -o \ + -name "build.gradle" \ + \) 2>/dev/null | grep -v node_modules | grep -v bin | grep -v obj +} + +# Find test projects +find_test_projects() { + local dir="${1:-.}" + find "$dir" -type f -name "*.Tests.csproj" 2>/dev/null | grep -v bin | grep -v obj +} + +# ============================================================================ +# Path Validation +# ============================================================================ + +# Check if path is under directory +path_under() { + local path="$1" + local dir="$2" + + local abs_path abs_dir + abs_path=$(absolute_path "$path") + abs_dir=$(absolute_path "$dir") + + [[ "$abs_path" == "$abs_dir"* ]] +} + +# Validate path is safe (no directory traversal) +path_is_safe() { + local path="$1" + local base="${2:-.}" + + # Check for obvious traversal attempts + if [[ "$path" == *".."* ]] || [[ "$path" == "/*" ]]; then + return 1 + fi + + # Verify resolved path is under base + path_under "$path" "$base" +} + +# ============================================================================ +# CI/CD Helpers +# ============================================================================ + +# Get artifact output directory +get_artifact_dir() { + local name="${1:-artifacts}" + local base="${GITHUB_WORKSPACE:-$(pwd)}" + echo "${base}/out/${name}" +} + +# Get test results directory +get_test_results_dir() { + local base="${GITHUB_WORKSPACE:-$(pwd)}" + echo "${base}/TestResults" +} + +# Ensure artifact directory exists and return path +ensure_artifact_dir() { + local name="${1:-artifacts}" + local dir + dir=$(get_artifact_dir "$name") + ensure_directory "$dir" + echo "$dir" +} diff --git a/devops/scripts/local-ci.ps1 b/devops/scripts/local-ci.ps1 new file mode 100644 index 000000000..d14ecbf1e --- /dev/null +++ b/devops/scripts/local-ci.ps1 @@ -0,0 +1,264 @@ +<# +.SYNOPSIS + Local CI Runner for Windows + PowerShell wrapper for local-ci.sh + +.DESCRIPTION + Unified local CI/CD testing runner for StellaOps on Windows. + This script wraps the Bash implementation via WSL2 or Git Bash. + +.PARAMETER Mode + The testing mode to run: + - smoke : Quick smoke test (unit tests only, ~2 min) + - pr : Full PR-gating suite (all required checks, ~15 min) + - module : Module-specific tests (auto-detect or specified) + - workflow : Simulate specific workflow via act + - release : Release simulation (dry-run) + - full : All tests including extended categories (~45 min) + +.PARAMETER Category + Specific test category to run (Unit, Architecture, Contract, Integration, Security, Golden) + +.PARAMETER Module + Specific module to test (Scanner, Concelier, Authority, etc.) + +.PARAMETER Workflow + Specific workflow to simulate (for workflow mode) + +.PARAMETER Docker + Force Docker execution mode + +.PARAMETER Native + Force native execution mode + +.PARAMETER Act + Force act execution mode + +.PARAMETER Parallel + Number of parallel test runners (default: auto-detect) + +.PARAMETER Verbose + Enable verbose output + +.PARAMETER DryRun + Show what would run without executing + +.PARAMETER Rebuild + Force rebuild of CI Docker image + +.PARAMETER NoServices + Skip starting CI services + +.PARAMETER KeepServices + Don't stop services after tests + +.EXAMPLE + .\local-ci.ps1 smoke + Quick validation before push + +.EXAMPLE + .\local-ci.ps1 pr + Full PR check + +.EXAMPLE + .\local-ci.ps1 module -Module Scanner + Test specific module + +.EXAMPLE + .\local-ci.ps1 workflow -Workflow test-matrix + Simulate specific workflow + +.NOTES + Requires WSL2 or Git Bash to execute the underlying Bash script. + For full feature support, use WSL2 with Ubuntu. +#> + +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [ValidateSet('smoke', 'pr', 'module', 'workflow', 'release', 'full')] + [string]$Mode = 'smoke', + + [string]$Category, + [string]$Module, + [string]$Workflow, + + [switch]$Docker, + [switch]$Native, + [switch]$Act, + + [int]$Parallel, + + [switch]$Verbose, + [switch]$DryRun, + [switch]$Rebuild, + [switch]$NoServices, + [switch]$KeepServices, + + [switch]$Help +) + +# Script location +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir) + +# Show help if requested +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +function Write-ColoredOutput { + param( + [string]$Message, + [ConsoleColor]$Color = [ConsoleColor]::White + ) + $originalColor = $Host.UI.RawUI.ForegroundColor + $Host.UI.RawUI.ForegroundColor = $Color + Write-Host $Message + $Host.UI.RawUI.ForegroundColor = $originalColor +} + +function Write-Info { Write-ColoredOutput "[INFO] $args" -Color Cyan } +function Write-Success { Write-ColoredOutput "[OK] $args" -Color Green } +function Write-Warning { Write-ColoredOutput "[WARN] $args" -Color Yellow } +function Write-Error { Write-ColoredOutput "[ERROR] $args" -Color Red } + +# Find Bash executable +function Find-BashExecutable { + # Priority: WSL2 > Git Bash > Windows Subsystem for Linux (legacy) + + # Check for WSL + $wsl = Get-Command wsl -ErrorAction SilentlyContinue + if ($wsl) { + # Verify WSL is working + $wslCheck = & wsl --status 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Info "Using WSL2 for Bash execution" + return @{ Type = 'wsl'; Path = 'wsl' } + } + } + + # Check for Git Bash + $gitBashPaths = @( + "C:\Program Files\Git\bin\bash.exe", + "C:\Program Files (x86)\Git\bin\bash.exe", + "$env:LOCALAPPDATA\Programs\Git\bin\bash.exe" + ) + + foreach ($path in $gitBashPaths) { + if (Test-Path $path) { + Write-Info "Using Git Bash for execution" + return @{ Type = 'gitbash'; Path = $path } + } + } + + # Check PATH for bash + $bashInPath = Get-Command bash -ErrorAction SilentlyContinue + if ($bashInPath) { + Write-Info "Using Bash from PATH" + return @{ Type = 'path'; Path = $bashInPath.Source } + } + + return $null +} + +# Convert Windows path to Unix path for WSL +function Convert-ToUnixPath { + param([string]$WindowsPath) + + if ($WindowsPath -match '^([A-Za-z]):(.*)$') { + $drive = $Matches[1].ToLower() + $rest = $Matches[2] -replace '\\', '/' + return "/mnt/$drive$rest" + } + return $WindowsPath -replace '\\', '/' +} + +# Build argument list +function Build-Arguments { + $args = @($Mode) + + if ($Category) { $args += "--category"; $args += $Category } + if ($Module) { $args += "--module"; $args += $Module } + if ($Workflow) { $args += "--workflow"; $args += $Workflow } + if ($Docker) { $args += "--docker" } + if ($Native) { $args += "--native" } + if ($Act) { $args += "--act" } + if ($Parallel) { $args += "--parallel"; $args += $Parallel } + if ($Verbose) { $args += "--verbose" } + if ($DryRun) { $args += "--dry-run" } + if ($Rebuild) { $args += "--rebuild" } + if ($NoServices) { $args += "--no-services" } + if ($KeepServices) { $args += "--keep-services" } + + return $args +} + +# Main execution +Write-Host "" +Write-Host "=========================================" -ForegroundColor Magenta +Write-Host " StellaOps Local CI Runner (Windows) " -ForegroundColor Magenta +Write-Host "=========================================" -ForegroundColor Magenta +Write-Host "" + +# Find Bash +$bash = Find-BashExecutable +if (-not $bash) { + Write-Error "Bash not found. Please install one of the following:" + Write-Host " - WSL2: https://docs.microsoft.com/en-us/windows/wsl/install" + Write-Host " - Git for Windows: https://git-scm.com/download/win" + exit 1 +} + +# Build script path +$scriptPath = Join-Path $ScriptDir "local-ci.sh" +if (-not (Test-Path $scriptPath)) { + Write-Error "Script not found: $scriptPath" + exit 1 +} + +# Build arguments +$bashArgs = Build-Arguments + +Write-Info "Mode: $Mode" +Write-Info "Bash: $($bash.Type)" +Write-Info "Repository: $RepoRoot" +Write-Host "" + +# Execute based on Bash type +try { + switch ($bash.Type) { + 'wsl' { + $unixScript = Convert-ToUnixPath $scriptPath + Write-Info "Executing: wsl bash $unixScript $($bashArgs -join ' ')" + & wsl bash $unixScript @bashArgs + } + 'gitbash' { + # Git Bash uses its own path conversion + $unixScript = $scriptPath -replace '\\', '/' + Write-Info "Executing: $($bash.Path) $unixScript $($bashArgs -join ' ')" + & $bash.Path $unixScript @bashArgs + } + 'path' { + Write-Info "Executing: bash $scriptPath $($bashArgs -join ' ')" + & bash $scriptPath @bashArgs + } + } + + $exitCode = $LASTEXITCODE +} +catch { + Write-Error "Execution failed: $_" + $exitCode = 1 +} + +# Report result +Write-Host "" +if ($exitCode -eq 0) { + Write-Success "Local CI completed successfully!" +} else { + Write-Error "Local CI failed with exit code: $exitCode" +} + +exit $exitCode diff --git a/devops/scripts/local-ci.sh b/devops/scripts/local-ci.sh new file mode 100644 index 000000000..cbc0bdfbb --- /dev/null +++ b/devops/scripts/local-ci.sh @@ -0,0 +1,818 @@ +#!/usr/bin/env bash +# ============================================================================= +# LOCAL CI RUNNER +# ============================================================================= +# Unified local CI/CD testing runner for StellaOps. +# +# Usage: +# ./devops/scripts/local-ci.sh [mode] [options] +# +# Modes: +# smoke - Quick smoke test (unit tests only, ~2 min) +# pr - Full PR-gating suite (all required checks, ~15 min) +# module - Module-specific tests (auto-detect or specified) +# workflow - Simulate specific workflow via act +# release - Release simulation (dry-run) +# full - All tests including extended categories (~45 min) +# +# Options: +# --category Run specific test category +# --workflow Specific workflow to simulate +# --module Specific module to test +# --docker Force Docker execution +# --native Force native execution +# --act Force act execution +# --parallel Parallel test runners (default: CPU count) +# --verbose Verbose output +# --dry-run Show what would run without executing +# --rebuild Force rebuild of CI Docker image +# --no-services Skip starting CI services +# --keep-services Don't stop services after tests +# --help Show this help message +# +# Examples: +# ./local-ci.sh smoke # Quick validation +# ./local-ci.sh pr # Full PR check +# ./local-ci.sh module --module Scanner # Test Scanner module +# ./local-ci.sh workflow --workflow test-matrix +# ./local-ci.sh release --dry-run +# +# ============================================================================= + +set -euo pipefail + +# ============================================================================= +# SCRIPT INITIALIZATION +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +export REPO_ROOT + +# Source libraries +source "$SCRIPT_DIR/lib/ci-common.sh" +source "$SCRIPT_DIR/lib/ci-docker.sh" +source "$SCRIPT_DIR/lib/ci-web.sh" 2>/dev/null || true # Web testing utilities + +# ============================================================================= +# CONSTANTS +# ============================================================================= + +# Modes +MODE_SMOKE="smoke" +MODE_PR="pr" +MODE_MODULE="module" +MODE_WORKFLOW="workflow" +MODE_RELEASE="release" +MODE_FULL="full" + +# Test categories +PR_GATING_CATEGORIES=(Unit Architecture Contract Integration Security Golden) +EXTENDED_CATEGORIES=(Performance Benchmark AirGap Chaos Determinism Resilience Observability) +ALL_CATEGORIES=("${PR_GATING_CATEGORIES[@]}" "${EXTENDED_CATEGORIES[@]}") + +# Default configuration +RESULTS_DIR="$REPO_ROOT/out/local-ci" +TRX_DIR="$RESULTS_DIR/trx" +LOGS_DIR="$RESULTS_DIR/logs" + +# ============================================================================= +# CONFIGURATION +# ============================================================================= + +MODE="" +EXECUTION_ENGINE="" # docker, native, act +SPECIFIC_CATEGORY="" +SPECIFIC_MODULE="" +SPECIFIC_WORKFLOW="" +PARALLEL_JOBS="" +VERBOSE=false +DRY_RUN=false +REBUILD_IMAGE=false +SKIP_SERVICES=false +KEEP_SERVICES=false + +# ============================================================================= +# USAGE +# ============================================================================= + +usage() { + cat < Run specific test category (${ALL_CATEGORIES[*]}) + --workflow Specific workflow to simulate (for workflow mode) + --module Specific module to test (for module mode) + --docker Force Docker execution + --native Force native execution + --act Force act execution + --parallel Parallel test runners (default: auto-detect) + --verbose Verbose output + --dry-run Show what would run without executing + --rebuild Force rebuild of CI Docker image + --no-services Skip starting CI services + --keep-services Don't stop services after tests + --help Show this help message + +Examples: + $(basename "$0") smoke # Quick validation before push + $(basename "$0") pr # Full PR check + $(basename "$0") pr --category Unit # Only run Unit tests + $(basename "$0") module # Auto-detect changed modules + $(basename "$0") module --module Scanner # Test specific module + $(basename "$0") workflow --workflow test-matrix + $(basename "$0") release --dry-run + $(basename "$0") pr --verbose --docker + +Test Categories: + PR-Gating: ${PR_GATING_CATEGORIES[*]} + Extended: ${EXTENDED_CATEGORIES[*]} +EOF +} + +# ============================================================================= +# ARGUMENT PARSING +# ============================================================================= + +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + smoke|pr|module|workflow|release|full) + MODE="$1" + shift + ;; + --category) + SPECIFIC_CATEGORY="$2" + shift 2 + ;; + --workflow) + SPECIFIC_WORKFLOW="$2" + shift 2 + ;; + --module) + SPECIFIC_MODULE="$2" + shift 2 + ;; + --docker) + EXECUTION_ENGINE="docker" + shift + ;; + --native) + EXECUTION_ENGINE="native" + shift + ;; + --act) + EXECUTION_ENGINE="act" + shift + ;; + --parallel) + PARALLEL_JOBS="$2" + shift 2 + ;; + --verbose|-v) + VERBOSE=true + shift + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --rebuild) + REBUILD_IMAGE=true + shift + ;; + --no-services) + SKIP_SERVICES=true + shift + ;; + --keep-services) + KEEP_SERVICES=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + log_error "Unknown option: $1" + usage + exit 1 + ;; + esac + done + + # Default mode is smoke + if [[ -z "$MODE" ]]; then + MODE="$MODE_SMOKE" + fi + + # Default execution engine based on mode + if [[ -z "$EXECUTION_ENGINE" ]]; then + case "$MODE" in + workflow) + EXECUTION_ENGINE="act" + ;; + *) + EXECUTION_ENGINE="native" + ;; + esac + fi + + # Auto-detect parallel jobs + if [[ -z "$PARALLEL_JOBS" ]]; then + PARALLEL_JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) + fi + + export VERBOSE +} + +# ============================================================================= +# DEPENDENCY CHECKS +# ============================================================================= + +check_dependencies() { + log_subsection "Checking Dependencies" + + local missing=0 + + # Always required + if ! require_command "dotnet" "https://dot.net/download"; then + missing=1 + else + local dotnet_version + dotnet_version=$(dotnet --version 2>/dev/null || echo "unknown") + log_debug "dotnet version: $dotnet_version" + fi + + if ! require_command "git"; then + missing=1 + fi + + # Docker required for docker mode + if [[ "$EXECUTION_ENGINE" == "docker" ]]; then + if ! check_docker; then + missing=1 + fi + fi + + # Act required for workflow mode + if [[ "$EXECUTION_ENGINE" == "act" ]] || [[ "$MODE" == "$MODE_WORKFLOW" ]]; then + if ! require_command "act" "brew install act (macOS) or https://github.com/nektos/act"; then + log_warn "act not found - workflow simulation will be limited" + fi + fi + + # Check for solution file + if ! require_file "$REPO_ROOT/src/StellaOps.sln"; then + missing=1 + fi + + return $missing +} + +# ============================================================================= +# RESULT INITIALIZATION +# ============================================================================= + +init_results() { + ensure_dir "$RESULTS_DIR" + ensure_dir "$TRX_DIR" + ensure_dir "$LOGS_DIR" + + # Create run metadata + local run_id + run_id=$(date +%Y%m%d_%H%M%S) + export RUN_ID="$run_id" + + log_debug "Results directory: $RESULTS_DIR" + log_debug "Run ID: $RUN_ID" +} + +# ============================================================================= +# TEST EXECUTION +# ============================================================================= + +run_dotnet_tests() { + local category="$1" + local filter="Category=$category" + + log_subsection "Running $category Tests" + + local trx_file="$TRX_DIR/${category}-${RUN_ID}.trx" + local log_file="$LOGS_DIR/${category}-${RUN_ID}.log" + + local test_cmd=( + dotnet test "$REPO_ROOT/src/StellaOps.sln" + --filter "$filter" + --configuration Release + --no-build + --logger "trx;LogFileName=$trx_file" + --results-directory "$TRX_DIR" + --verbosity minimal + ) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would execute: ${test_cmd[*]}" + return 0 + fi + + local start_time + start_time=$(start_timer) + + if [[ "$VERBOSE" == "true" ]]; then + "${test_cmd[@]}" 2>&1 | tee "$log_file" + else + "${test_cmd[@]}" > "$log_file" 2>&1 + fi + + local result=$? + stop_timer "$start_time" "$category tests" + + if [[ $result -eq 0 ]]; then + log_success "$category tests passed" + else + log_error "$category tests failed (see $log_file)" + fi + + return $result +} + +run_dotnet_build() { + log_subsection "Building Solution" + + local build_cmd=( + dotnet build "$REPO_ROOT/src/StellaOps.sln" + --configuration Release + ) + + if [[ "$DRY_RUN" == "true" ]]; then + log_info "[DRY-RUN] Would execute: ${build_cmd[*]}" + return 0 + fi + + local start_time + start_time=$(start_timer) + + "${build_cmd[@]}" + + local result=$? + stop_timer "$start_time" "Build" + + if [[ $result -eq 0 ]]; then + log_success "Build completed successfully" + else + log_error "Build failed" + fi + + return $result +} + +# ============================================================================= +# MODE IMPLEMENTATIONS +# ============================================================================= + +run_smoke_mode() { + log_section "Smoke Test Mode" + log_info "Running quick validation (Unit tests only)" + + local start_time + start_time=$(start_timer) + + # Build + run_dotnet_build || return 1 + + # Run Unit tests only + run_dotnet_tests "Unit" + local result=$? + + stop_timer "$start_time" "Smoke test" + return $result +} + +run_pr_mode() { + log_section "PR-Gating Mode" + log_info "Running full PR-gating suite" + log_info "Categories: ${PR_GATING_CATEGORIES[*]}" + + local start_time + start_time=$(start_timer) + local failed=0 + local results=() + + # Check if Web module has changes + local web_changed=false + local changed_files + changed_files=$(get_changed_files main 2>/dev/null || echo "") + if echo "$changed_files" | grep -q "^src/Web/"; then + web_changed=true + log_info "Web module changes detected - will run Web tests" + fi + + # Start services if needed + if [[ "$SKIP_SERVICES" != "true" ]]; then + start_ci_services postgres-ci valkey-ci || { + log_warn "Failed to start services, continuing anyway..." + } + fi + + # Build .NET solution + run_dotnet_build || return 1 + + # Run each .NET category + if [[ -n "$SPECIFIC_CATEGORY" ]]; then + if [[ "$SPECIFIC_CATEGORY" == "Web" ]] || [[ "$SPECIFIC_CATEGORY" == "web" ]]; then + # Run Web tests only + if type run_web_pr_gating &>/dev/null; then + run_web_pr_gating + results+=("Web:$?") + fi + else + run_dotnet_tests "$SPECIFIC_CATEGORY" + results+=("$SPECIFIC_CATEGORY:$?") + fi + else + for category in "${PR_GATING_CATEGORIES[@]}"; do + run_dotnet_tests "$category" + local cat_result=$? + results+=("$category:$cat_result") + if [[ $cat_result -ne 0 ]]; then + failed=1 + fi + done + + # Run Web tests if Web module changed + if [[ "$web_changed" == "true" ]]; then + log_subsection "Web Module Tests" + if type run_web_pr_gating &>/dev/null; then + run_web_pr_gating + local web_result=$? + results+=("Web:$web_result") + if [[ $web_result -ne 0 ]]; then + failed=1 + fi + else + log_warn "Web testing library not loaded" + fi + fi + fi + + # Stop services + if [[ "$SKIP_SERVICES" != "true" ]] && [[ "$KEEP_SERVICES" != "true" ]]; then + stop_ci_services + fi + + # Print summary + log_section "PR-Gating Results" + for result in "${results[@]}"; do + local name="${result%%:*}" + local status="${result##*:}" + if [[ "$status" == "0" ]]; then + print_status "$name" "true" + else + print_status "$name" "false" + fi + done + + stop_timer "$start_time" "PR-gating suite" + return $failed +} + +run_module_mode() { + log_section "Module-Specific Mode" + + local modules_to_test=() + local has_dotnet_modules=false + local has_node_modules=false + + if [[ -n "$SPECIFIC_MODULE" ]]; then + modules_to_test=("$SPECIFIC_MODULE") + log_info "Testing specified module: $SPECIFIC_MODULE" + else + log_info "Auto-detecting changed modules..." + local detected + detected=$(detect_changed_modules main) + + if [[ "$detected" == "ALL" ]]; then + log_info "Infrastructure changes detected - running all tests" + run_pr_mode + return $? + elif [[ "$detected" == "NONE" ]]; then + log_info "No module changes detected" + return 0 + else + read -ra modules_to_test <<< "$detected" + log_info "Detected changed modules: ${modules_to_test[*]}" + fi + fi + + # Categorize modules + for module in "${modules_to_test[@]}"; do + if [[ " ${NODE_MODULES[*]} " =~ " ${module} " ]]; then + has_node_modules=true + else + has_dotnet_modules=true + fi + done + + local start_time + start_time=$(start_timer) + local failed=0 + + # Build .NET solution if we have .NET modules + if [[ "$has_dotnet_modules" == "true" ]]; then + run_dotnet_build || return 1 + fi + + for module in "${modules_to_test[@]}"; do + log_subsection "Testing Module: $module" + + # Check if this is a Node.js module (Web, DevPortal) + if [[ " ${NODE_MODULES[*]} " =~ " ${module} " ]]; then + log_info "Running Node.js tests for $module" + + case "$module" in + Web) + if type run_web_pr_gating &>/dev/null; then + run_web_pr_gating || failed=1 + else + log_warn "Web testing library not loaded - running basic npm test" + pushd "$REPO_ROOT/src/Web/StellaOps.Web" > /dev/null 2>&1 || continue + npm ci --prefer-offline --no-audit 2>/dev/null || npm install + npm run test:ci || failed=1 + popd > /dev/null + fi + ;; + DevPortal) + local portal_dir="$REPO_ROOT/src/DevPortal/StellaOps.DevPortal.Site" + if [[ -d "$portal_dir" ]]; then + pushd "$portal_dir" > /dev/null || continue + npm ci --prefer-offline --no-audit 2>/dev/null || npm install + npm test 2>/dev/null || log_warn "DevPortal tests not configured" + popd > /dev/null + fi + ;; + esac + continue + fi + + # .NET module handling + local test_paths="${MODULE_PATHS[$module]:-}" + if [[ -z "$test_paths" ]]; then + log_warn "Unknown module: $module" + continue + fi + + # Run tests for each path + for path in $test_paths; do + local test_dir="$REPO_ROOT/$path/__Tests" + if [[ -d "$test_dir" ]]; then + log_info "Running tests in: $test_dir" + + local test_projects + test_projects=$(find "$test_dir" -name "*.Tests.csproj" -type f 2>/dev/null) + + for project in $test_projects; do + log_debug "Testing: $project" + dotnet test "$project" --configuration Release --no-build --verbosity minimal || { + failed=1 + } + done + fi + done + done + + stop_timer "$start_time" "Module tests" + return $failed +} + +run_workflow_mode() { + log_section "Workflow Simulation Mode" + + if [[ -z "$SPECIFIC_WORKFLOW" ]]; then + log_error "No workflow specified. Use --workflow " + log_info "Example: --workflow test-matrix" + return 1 + fi + + local workflow_file="$REPO_ROOT/.gitea/workflows/${SPECIFIC_WORKFLOW}.yml" + if [[ ! -f "$workflow_file" ]]; then + # Try without .yml extension + workflow_file="$REPO_ROOT/.gitea/workflows/${SPECIFIC_WORKFLOW}" + if [[ ! -f "$workflow_file" ]]; then + log_error "Workflow not found: $SPECIFIC_WORKFLOW" + log_info "Available workflows:" + ls -1 "$REPO_ROOT/.gitea/workflows/"*.yml 2>/dev/null | xargs -n1 basename | head -20 + return 1 + fi + fi + + log_info "Simulating workflow: $SPECIFIC_WORKFLOW" + log_info "Workflow file: $workflow_file" + + if ! command -v act &>/dev/null; then + log_error "act is required for workflow simulation" + log_info "Install with: brew install act (macOS)" + return 1 + fi + + # Build CI image if needed + if [[ "$REBUILD_IMAGE" == "true" ]] || ! ci_image_exists; then + build_ci_image "$REBUILD_IMAGE" || return 1 + fi + + local event_file="$REPO_ROOT/devops/ci-local/events/pull-request.json" + local actrc_file="$REPO_ROOT/.actrc" + + local act_args=( + -W "$workflow_file" + --platform "ubuntu-22.04=$CI_IMAGE" + --platform "ubuntu-latest=$CI_IMAGE" + --env "DOTNET_NOLOGO=1" + --env "DOTNET_CLI_TELEMETRY_OPTOUT=1" + --env "TZ=UTC" + --bind + ) + + if [[ -f "$event_file" ]]; then + act_args+=(--eventpath "$event_file") + fi + + if [[ -f "$REPO_ROOT/devops/ci-local/.env.local" ]]; then + act_args+=(--env-file "$REPO_ROOT/devops/ci-local/.env.local") + fi + + if [[ "$DRY_RUN" == "true" ]]; then + act_args+=(-n) + fi + + if [[ "$VERBOSE" == "true" ]]; then + act_args+=(--verbose) + fi + + log_info "Running: act ${act_args[*]}" + act "${act_args[@]}" +} + +run_release_mode() { + log_section "Release Simulation Mode" + log_info "Running release dry-run" + + if [[ "$DRY_RUN" != "true" ]]; then + log_warn "Release mode always runs as dry-run for safety" + DRY_RUN=true + fi + + local start_time + start_time=$(start_timer) + + # Build all modules + log_subsection "Building All Modules" + run_dotnet_build || return 1 + + # Package CLI + log_subsection "Packaging CLI" + local cli_project="$REPO_ROOT/src/Cli/StellaOps.Cli/StellaOps.Cli.csproj" + if [[ -f "$cli_project" ]]; then + log_info "[DRY-RUN] Would build CLI for: linux-x64, linux-arm64, osx-arm64, win-x64" + fi + + # Validate Helm chart + log_subsection "Validating Helm Chart" + if command -v helm &>/dev/null; then + local helm_chart="$REPO_ROOT/devops/helm/stellaops" + if [[ -d "$helm_chart" ]]; then + helm lint "$helm_chart" || log_warn "Helm lint warnings" + fi + else + log_info "helm not found - skipping chart validation" + fi + + # Generate release manifest + log_subsection "Release Manifest" + log_info "[DRY-RUN] Would generate:" + log_info " - Release notes" + log_info " - Changelog" + log_info " - Docker Compose files" + log_info " - SBOM" + log_info " - Checksums" + + stop_timer "$start_time" "Release simulation" + return 0 +} + +run_full_mode() { + log_section "Full Test Mode" + log_info "Running all tests including extended categories" + log_info "Categories: ${ALL_CATEGORIES[*]}" + + local start_time + start_time=$(start_timer) + local failed=0 + + # Start all services + if [[ "$SKIP_SERVICES" != "true" ]]; then + start_ci_services || { + log_warn "Failed to start services, continuing anyway..." + } + fi + + # Build + run_dotnet_build || return 1 + + # Run all categories + for category in "${ALL_CATEGORIES[@]}"; do + run_dotnet_tests "$category" || { + failed=1 + log_warn "Continuing after $category failure..." + } + done + + # Stop services + if [[ "$SKIP_SERVICES" != "true" ]] && [[ "$KEEP_SERVICES" != "true" ]]; then + stop_ci_services + fi + + stop_timer "$start_time" "Full test suite" + return $failed +} + +# ============================================================================= +# MAIN +# ============================================================================= + +main() { + parse_args "$@" + + log_section "StellaOps Local CI Runner" + log_info "Mode: $MODE" + log_info "Engine: $EXECUTION_ENGINE" + log_info "Parallel: $PARALLEL_JOBS jobs" + log_info "Repository: $REPO_ROOT" + + if [[ "$DRY_RUN" == "true" ]]; then + log_warn "DRY-RUN MODE - No changes will be made" + fi + + # Check dependencies + check_dependencies || exit 1 + + # Initialize results directory + init_results + + # Load environment + load_env_file "$REPO_ROOT/devops/ci-local/.env.local" || true + + # Run selected mode + case "$MODE" in + "$MODE_SMOKE") + run_smoke_mode + ;; + "$MODE_PR") + run_pr_mode + ;; + "$MODE_MODULE") + run_module_mode + ;; + "$MODE_WORKFLOW") + run_workflow_mode + ;; + "$MODE_RELEASE") + run_release_mode + ;; + "$MODE_FULL") + run_full_mode + ;; + *) + log_error "Unknown mode: $MODE" + usage + exit 1 + ;; + esac + + local result=$? + + log_section "Summary" + log_info "Results saved to: $RESULTS_DIR" + + if [[ $result -eq 0 ]]; then + log_success "All tests passed!" + else + log_error "Some tests failed" + fi + + return $result +} + +# Run main if executed directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/devops/scripts/migrations-reset-pre-1.0.sql b/devops/scripts/migrations-reset-pre-1.0.sql new file mode 100644 index 000000000..6c0be8ad6 --- /dev/null +++ b/devops/scripts/migrations-reset-pre-1.0.sql @@ -0,0 +1,244 @@ +-- ============================================================================ +-- StellaOps Migration Reset Script for Pre-1.0 Deployments +-- ============================================================================ +-- This script updates schema_migrations tables to recognize the 1.0.0 compacted +-- migrations for deployments that upgraded from pre-1.0 versions. +-- +-- Run via: psql -f migrations-reset-pre-1.0.sql +-- Or with connection: psql -h -U -d -f migrations-reset-pre-1.0.sql +-- ============================================================================ + +BEGIN; + +-- ============================================================================ +-- Authority Module Reset +-- ============================================================================ +-- Original: 001_initial_schema, 002_mongo_store_equivalents, 003_enable_rls, +-- 004_offline_kit_audit, 005_verdict_manifests +-- New: 001_initial_schema (compacted) + +DELETE FROM authority.schema_migrations +WHERE migration_name IN ( + '001_initial_schema.sql', + '002_mongo_store_equivalents.sql', + '003_enable_rls.sql', + '004_offline_kit_audit.sql', + '005_verdict_manifests.sql' +); + +INSERT INTO authority.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Scheduler Module Reset +-- ============================================================================ +-- Original: 001_initial_schema, 002_graph_jobs, 003_runs_policy, +-- 010_generated_columns_runs, 011_enable_rls, 012_partition_audit, +-- 012b_migrate_audit_data +-- New: 001_initial_schema (compacted) + +DELETE FROM scheduler.schema_migrations +WHERE migration_name IN ( + '001_initial_schema.sql', + '002_graph_jobs.sql', + '003_runs_policy.sql', + '010_generated_columns_runs.sql', + '011_enable_rls.sql', + '012_partition_audit.sql', + '012b_migrate_audit_data.sql' +); + +INSERT INTO scheduler.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Scanner Module Reset +-- ============================================================================ +-- Original: 001-034 plus various numbered files (27 total) +-- New: 001_initial_schema (compacted) + +DELETE FROM scanner.schema_migrations +WHERE migration_name IN ( + '001_create_tables.sql', + '002_proof_spine_tables.sql', + '003_classification_history.sql', + '004_scan_metrics.sql', + '005_smart_diff_tables.sql', + '006_score_replay_tables.sql', + '007_unknowns_ranking_containment.sql', + '008_epss_integration.sql', + '0059_scans_table.sql', + '0065_unknowns_table.sql', + '0075_scan_findings_table.sql', + '020_call_graph_tables.sql', + '021_smart_diff_tables_search_path.sql', + '022_reachability_drift_tables.sql', + '023_scanner_api_ingestion.sql', + '024_smart_diff_priority_score_widen.sql', + '025_epss_raw_layer.sql', + '026_epss_signal_layer.sql', + '027_witness_storage.sql', + '028_epss_triage_columns.sql', + '029_vuln_surfaces.sql', + '030_vuln_surface_triggers_update.sql', + '031_reach_cache.sql', + '032_idempotency_keys.sql', + '033_binary_evidence.sql', + '034_func_proof_tables.sql', + 'DM001_rename_scanner_migrations.sql' +); + +INSERT INTO scanner.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Policy Module Reset +-- ============================================================================ +-- Original: 001-013 (14 files, includes duplicate 010 prefix) +-- New: 001_initial_schema (compacted) + +DELETE FROM policy.schema_migrations +WHERE migration_name IN ( + '001_initial_schema.sql', + '002_cvss_receipts.sql', + '003_snapshots_violations.sql', + '004_epss_risk_scores.sql', + '005_cvss_multiversion.sql', + '006_enable_rls.sql', + '007_unknowns_registry.sql', + '008_exception_objects.sql', + '009_exception_applications.sql', + '010_recheck_evidence.sql', + '010_unknowns_blast_radius_containment.sql', + '011_unknowns_reason_codes.sql', + '012_budget_ledger.sql', + '013_exception_approval.sql' +); + +INSERT INTO policy.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Notify Module Reset +-- ============================================================================ +-- Original: 001_initial_schema, 010_enable_rls, 011_partition_deliveries, +-- 011b_migrate_deliveries_data +-- New: 001_initial_schema (compacted) + +DELETE FROM notify.schema_migrations +WHERE migration_name IN ( + '001_initial_schema.sql', + '010_enable_rls.sql', + '011_partition_deliveries.sql', + '011b_migrate_deliveries_data.sql' +); + +INSERT INTO notify.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Concelier Module Reset +-- ============================================================================ +-- Original: 17 migration files +-- New: 001_initial_schema (compacted) + +DELETE FROM concelier.schema_migrations +WHERE migration_name ~ '^[0-9]{3}_.*\.sql$'; + +INSERT INTO concelier.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Attestor Module Reset (proofchain + attestor schemas) +-- ============================================================================ +-- Original: 20251214000001_AddProofChainSchema.sql, 20251216_001_create_rekor_submission_queue.sql +-- New: 001_initial_schema (compacted) + +DELETE FROM proofchain.schema_migrations +WHERE migration_name IN ( + '20251214000001_AddProofChainSchema.sql', + '20251214000002_RollbackProofChainSchema.sql', + '20251216_001_create_rekor_submission_queue.sql' +); + +INSERT INTO proofchain.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Signer Module Reset +-- ============================================================================ +-- Original: 20251214000001_AddKeyManagementSchema.sql +-- New: 001_initial_schema (compacted) + +DELETE FROM signer.schema_migrations +WHERE migration_name IN ( + '20251214000001_AddKeyManagementSchema.sql' +); + +INSERT INTO signer.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Signals Module Reset +-- ============================================================================ +-- Original: V0000_001__extensions.sql, V1102_001__unknowns_scoring_schema.sql, +-- V1105_001__deploy_refs_graph_metrics.sql, V3102_001__callgraph_relational_tables.sql +-- New: 001_initial_schema (compacted) + +DELETE FROM signals.schema_migrations +WHERE migration_name IN ( + 'V0000_001__extensions.sql', + 'V1102_001__unknowns_scoring_schema.sql', + 'V1105_001__deploy_refs_graph_metrics.sql', + 'V3102_001__callgraph_relational_tables.sql' +); + +INSERT INTO signals.schema_migrations (migration_name, category, checksum, applied_at) +VALUES ('001_initial_schema.sql', 'startup', 'compacted_1.0.0', NOW()) +ON CONFLICT (migration_name) DO NOTHING; + +-- ============================================================================ +-- Verification +-- ============================================================================ +-- Display current migration status per module + +DO $$ +DECLARE + v_module TEXT; + v_count INT; +BEGIN + FOR v_module IN SELECT unnest(ARRAY['authority', 'scheduler', 'scanner', 'policy', 'notify', 'concelier', 'proofchain', 'signer', 'signals']) LOOP + EXECUTE format('SELECT COUNT(*) FROM %I.schema_migrations', v_module) INTO v_count; + RAISE NOTICE '% module: % migrations registered', v_module, v_count; + END LOOP; +END $$; + +COMMIT; + +-- ============================================================================ +-- Post-Reset Notes +-- ============================================================================ +-- After running this script: +-- 1. All modules should show exactly 1 migration registered +-- 2. The schema structure should be identical to a fresh 1.0.0 deployment +-- 3. Future migrations (002+) will apply normally +-- +-- To verify manually: +-- SELECT * FROM authority.schema_migrations; +-- SELECT * FROM scheduler.schema_migrations; +-- SELECT * FROM scanner.schema_migrations; +-- SELECT * FROM policy.schema_migrations; +-- SELECT * FROM notify.schema_migrations; +-- SELECT * FROM concelier.schema_migrations; +-- SELECT * FROM proofchain.schema_migrations; +-- SELECT * FROM signer.schema_migrations; +-- SELECT * FROM signals.schema_migrations; +-- ============================================================================ diff --git a/devops/scripts/regenerate-solution.ps1 b/devops/scripts/regenerate-solution.ps1 new file mode 100644 index 000000000..c8f4eb4f9 --- /dev/null +++ b/devops/scripts/regenerate-solution.ps1 @@ -0,0 +1,169 @@ +#!/usr/bin/env pwsh +# regenerate-solution.ps1 - Regenerate StellaOps.sln without duplicate projects +# +# This script: +# 1. Backs up the existing solution +# 2. Creates a new solution +# 3. Adds all .csproj files, skipping duplicates +# 4. Preserves solution folders where possible + +param( + [string]$SolutionPath = "src/StellaOps.sln", + [switch]$DryRun +) + +$ErrorActionPreference = "Stop" + +# Canonical locations for test projects (in priority order) +# Later entries win when there are duplicates +$canonicalPatterns = @( + # Module-local tests (highest priority) + "src/*/__Tests/*/*.csproj", + "src/*/__Libraries/__Tests/*/*.csproj", + "src/__Libraries/__Tests/*/*.csproj", + # Cross-module integration tests + "src/__Tests/Integration/*/*.csproj", + "src/__Tests/__Libraries/*/*.csproj", + # Category-based cross-module tests + "src/__Tests/chaos/*/*.csproj", + "src/__Tests/security/*/*.csproj", + "src/__Tests/interop/*/*.csproj", + "src/__Tests/parity/*/*.csproj", + "src/__Tests/reachability/*/*.csproj", + # Single global tests + "src/__Tests/*/*.csproj" +) + +Write-Host "=== Solution Regeneration Script ===" -ForegroundColor Cyan +Write-Host "Solution: $SolutionPath" +Write-Host "Dry Run: $DryRun" +Write-Host "" + +# Find all .csproj files +Write-Host "Finding all project files..." -ForegroundColor Yellow +$allProjects = Get-ChildItem -Path "src" -Filter "*.csproj" -Recurse | + Where-Object { $_.FullName -notmatch "\\obj\\" -and $_.FullName -notmatch "\\bin\\" } + +Write-Host "Found $($allProjects.Count) project files" + +# Build a map of project name -> list of paths +$projectMap = @{} +foreach ($proj in $allProjects) { + $name = $proj.BaseName + if (-not $projectMap.ContainsKey($name)) { + $projectMap[$name] = @() + } + $projectMap[$name] += $proj.FullName +} + +# Find duplicates +$duplicates = $projectMap.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 } +Write-Host "" +Write-Host "Found $($duplicates.Count) projects with duplicate names:" -ForegroundColor Yellow +foreach ($dup in $duplicates) { + Write-Host " $($dup.Key):" -ForegroundColor Red + foreach ($path in $dup.Value) { + Write-Host " - $path" + } +} + +# Select canonical path for each project +function Get-CanonicalPath { + param([string[]]$Paths) + + # Prefer module-local __Tests over global __Tests + $moduleTests = $Paths | Where-Object { $_ -match "src\\[^_][^\\]+\\__Tests\\" } + if ($moduleTests.Count -gt 0) { return $moduleTests[0] } + + # Prefer __Libraries/__Tests + $libTests = $Paths | Where-Object { $_ -match "__Libraries\\__Tests\\" } + if ($libTests.Count -gt 0) { return $libTests[0] } + + # Prefer __Tests over non-__Tests location in same parent + $testsPath = $Paths | Where-Object { $_ -match "\\__Tests\\" } + if ($testsPath.Count -gt 0) { return $testsPath[0] } + + # Otherwise, take first + return $Paths[0] +} + +# Build final project list +$finalProjects = @() +foreach ($entry in $projectMap.GetEnumerator()) { + $canonical = Get-CanonicalPath -Paths $entry.Value + $finalProjects += $canonical +} + +Write-Host "" +Write-Host "Final project count: $($finalProjects.Count)" -ForegroundColor Green + +if ($DryRun) { + Write-Host "" + Write-Host "=== DRY RUN - No changes made ===" -ForegroundColor Magenta + Write-Host "Would add the following projects to solution:" + $finalProjects | ForEach-Object { Write-Host " $_" } + exit 0 +} + +# Backup existing solution +$backupPath = "$SolutionPath.bak" +if (Test-Path $SolutionPath) { + Copy-Item $SolutionPath $backupPath -Force + Write-Host "Backed up existing solution to $backupPath" -ForegroundColor Gray +} + +# Create new solution +Write-Host "" +Write-Host "Creating new solution..." -ForegroundColor Yellow +$slnDir = Split-Path $SolutionPath -Parent +$slnName = [System.IO.Path]::GetFileNameWithoutExtension($SolutionPath) + +# Remove old solution +if (Test-Path $SolutionPath) { + Remove-Item $SolutionPath -Force +} + +# Create fresh solution +Push-Location $slnDir +dotnet new sln -n $slnName --force 2>$null +Pop-Location + +# Add projects in batches (dotnet sln add can handle multiple) +Write-Host "Adding projects to solution..." -ForegroundColor Yellow +$added = 0 +$failed = 0 + +foreach ($proj in $finalProjects) { + try { + $result = dotnet sln $SolutionPath add $proj 2>&1 + if ($LASTEXITCODE -eq 0) { + $added++ + if ($added % 50 -eq 0) { + Write-Host " Added $added projects..." -ForegroundColor Gray + } + } else { + Write-Host " Failed to add: $proj" -ForegroundColor Red + $failed++ + } + } catch { + Write-Host " Error adding: $proj - $_" -ForegroundColor Red + $failed++ + } +} + +Write-Host "" +Write-Host "=== Summary ===" -ForegroundColor Cyan +Write-Host "Projects added: $added" -ForegroundColor Green +Write-Host "Projects failed: $failed" -ForegroundColor $(if ($failed -gt 0) { "Red" } else { "Green" }) +Write-Host "" +Write-Host "Solution regenerated at: $SolutionPath" + +# Verify +Write-Host "" +Write-Host "Verifying solution..." -ForegroundColor Yellow +$verifyResult = dotnet build $SolutionPath --no-restore -t:ValidateSolutionConfiguration 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Host "Solution validation passed!" -ForegroundColor Green +} else { + Write-Host "Solution validation had issues - check manually" -ForegroundColor Yellow +} diff --git a/devops/scripts/remove-stale-refs.ps1 b/devops/scripts/remove-stale-refs.ps1 new file mode 100644 index 000000000..1b1a9f1a5 --- /dev/null +++ b/devops/scripts/remove-stale-refs.ps1 @@ -0,0 +1,70 @@ +#!/usr/bin/env pwsh +# remove-stale-refs.ps1 - Remove stale project references that don't exist + +param([string]$SlnPath = "src/StellaOps.sln") + +$content = Get-Content $SlnPath -Raw +$lines = $content -split "`r?`n" + +# Stale project paths (relative from solution location) +$staleProjects = @( + "__Tests\AirGap\StellaOps.AirGap.Controller.Tests", + "__Tests\AirGap\StellaOps.AirGap.Importer.Tests", + "__Tests\AirGap\StellaOps.AirGap.Time.Tests", + "__Tests\StellaOps.Gateway.WebService.Tests", + "__Tests\Graph\StellaOps.Graph.Indexer.Tests", + "Scanner\StellaOps.Scanner.Analyzers.Native", + "__Libraries\__Tests\StellaOps.Signals.Tests", + "__Tests\StellaOps.Audit.ReplayToken.Tests", + "__Tests\StellaOps.Router.Gateway.Tests", + "__Libraries\StellaOps.Cryptography" +) + +$staleGuids = @() +$newLines = @() +$skipNext = $false + +for ($i = 0; $i -lt $lines.Count; $i++) { + $line = $lines[$i] + + if ($skipNext) { + $skipNext = $false + continue + } + + $isStale = $false + foreach ($stalePath in $staleProjects) { + if ($line -like "*$stalePath*") { + # Extract GUID + if ($line -match '\{([A-F0-9-]+)\}"?$') { + $staleGuids += $Matches[1] + } + Write-Host "Removing stale: $stalePath" + $isStale = $true + $skipNext = $true + break + } + } + + if (-not $isStale) { + $newLines += $line + } +} + +# Remove GlobalSection references to stale GUIDs +$finalLines = @() +foreach ($line in $newLines) { + $skip = $false + foreach ($guid in $staleGuids) { + if ($line -match $guid) { + $skip = $true + break + } + } + if (-not $skip) { + $finalLines += $line + } +} + +$finalLines -join "`r`n" | Set-Content $SlnPath -Encoding UTF8 -NoNewline +Write-Host "Removed $($staleGuids.Count) stale project references" diff --git a/devops/scripts/restore-deleted-tests.ps1 b/devops/scripts/restore-deleted-tests.ps1 new file mode 100644 index 000000000..7a423aafc --- /dev/null +++ b/devops/scripts/restore-deleted-tests.ps1 @@ -0,0 +1,61 @@ +# Restore deleted test files from commit parent +# Maps old locations to new locations + +$ErrorActionPreference = "Stop" +$parentCommit = "74c7aa250c401ee9ac332686832b256159efa604^" + +# Mapping: old path -> new path +$mappings = @{ + "src/__Tests/AirGap/StellaOps.AirGap.Importer.Tests" = "src/AirGap/__Tests/StellaOps.AirGap.Importer.Tests" + "src/__Tests/AirGap/StellaOps.AirGap.Controller.Tests" = "src/AirGap/__Tests/StellaOps.AirGap.Controller.Tests" + "src/__Tests/AirGap/StellaOps.AirGap.Time.Tests" = "src/AirGap/__Tests/StellaOps.AirGap.Time.Tests" + "src/__Tests/StellaOps.Gateway.WebService.Tests" = "src/Gateway/__Tests/StellaOps.Gateway.WebService.Tests" + "src/__Tests/Replay/StellaOps.Replay.Core.Tests" = "src/Replay/__Tests/StellaOps.Replay.Core.Tests" + "src/__Tests/Provenance/StellaOps.Provenance.Attestation.Tests" = "src/Provenance/__Tests/StellaOps.Provenance.Attestation.Tests" + "src/__Tests/Policy/StellaOps.Policy.Scoring.Tests" = "src/Policy/__Tests/StellaOps.Policy.Scoring.Tests" +} + +Set-Location "E:\dev\git.stella-ops.org" + +foreach ($mapping in $mappings.GetEnumerator()) { + $oldPath = $mapping.Key + $newPath = $mapping.Value + + Write-Host "`nProcessing: $oldPath -> $newPath" -ForegroundColor Cyan + + # Get list of files from old location in git + $files = git ls-tree -r --name-only "$parentCommit" -- $oldPath 2>$null + + if (-not $files) { + Write-Host " No files found at old path" -ForegroundColor Yellow + continue + } + + foreach ($file in $files) { + # Calculate relative path and new file path + $relativePath = $file.Substring($oldPath.Length + 1) + $newFilePath = Join-Path $newPath $relativePath + + # Create directory if needed + $newDir = Split-Path $newFilePath -Parent + if (-not (Test-Path $newDir)) { + New-Item -ItemType Directory -Path $newDir -Force | Out-Null + } + + # Check if file exists + if (Test-Path $newFilePath) { + Write-Host " Exists: $relativePath" -ForegroundColor DarkGray + continue + } + + # Restore file + git show "${parentCommit}:${file}" > $newFilePath 2>$null + if ($LASTEXITCODE -eq 0) { + Write-Host " Restored: $relativePath" -ForegroundColor Green + } else { + Write-Host " Failed: $relativePath" -ForegroundColor Red + } + } +} + +Write-Host "`nDone!" -ForegroundColor Cyan diff --git a/devops/scripts/validate-before-commit.ps1 b/devops/scripts/validate-before-commit.ps1 new file mode 100644 index 000000000..013411567 --- /dev/null +++ b/devops/scripts/validate-before-commit.ps1 @@ -0,0 +1,176 @@ +<# +.SYNOPSIS + Pre-Commit Validation Script for Windows + +.DESCRIPTION + Run this script before committing to ensure all CI checks will pass. + Wraps the Bash validation script via WSL2 or Git Bash. + +.PARAMETER Level + Validation level: + - quick : Smoke test only (~2 min) + - pr : Full PR-gating suite (~15 min) [default] + - full : All tests including extended (~45 min) + +.EXAMPLE + .\validate-before-commit.ps1 + Run PR-gating validation + +.EXAMPLE + .\validate-before-commit.ps1 quick + Run quick smoke test only + +.EXAMPLE + .\validate-before-commit.ps1 full + Run full test suite +#> + +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [ValidateSet('quick', 'pr', 'full')] + [string]$Level = 'pr', + + [switch]$Help +) + +# Script location +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = Split-Path -Parent (Split-Path -Parent $ScriptDir) + +if ($Help) { + Get-Help $MyInvocation.MyCommand.Path -Detailed + exit 0 +} + +# Colors +function Write-ColoredOutput { + param( + [string]$Message, + [ConsoleColor]$Color = [ConsoleColor]::White + ) + $originalColor = $Host.UI.RawUI.ForegroundColor + $Host.UI.RawUI.ForegroundColor = $Color + Write-Host $Message + $Host.UI.RawUI.ForegroundColor = $originalColor +} + +function Write-Header { + param([string]$Message) + Write-Host "" + Write-ColoredOutput "=============================================" -Color Cyan + Write-ColoredOutput " $Message" -Color Cyan + Write-ColoredOutput "=============================================" -Color Cyan + Write-Host "" +} + +function Write-Step { Write-ColoredOutput ">>> $args" -Color Blue } +function Write-Pass { Write-ColoredOutput "[PASS] $args" -Color Green } +function Write-Fail { Write-ColoredOutput "[FAIL] $args" -Color Red } +function Write-Warn { Write-ColoredOutput "[WARN] $args" -Color Yellow } +function Write-Info { Write-ColoredOutput "[INFO] $args" -Color Cyan } + +# Find Bash +function Find-BashExecutable { + # Check WSL + $wsl = Get-Command wsl -ErrorAction SilentlyContinue + if ($wsl) { + $wslCheck = & wsl --status 2>&1 + if ($LASTEXITCODE -eq 0) { + return @{ Type = 'wsl'; Path = 'wsl' } + } + } + + # Check Git Bash + $gitBashPaths = @( + "C:\Program Files\Git\bin\bash.exe", + "C:\Program Files (x86)\Git\bin\bash.exe", + "$env:LOCALAPPDATA\Programs\Git\bin\bash.exe" + ) + + foreach ($path in $gitBashPaths) { + if (Test-Path $path) { + return @{ Type = 'gitbash'; Path = $path } + } + } + + return $null +} + +function Convert-ToUnixPath { + param([string]$WindowsPath) + if ($WindowsPath -match '^([A-Za-z]):(.*)$') { + $drive = $Matches[1].ToLower() + $rest = $Matches[2] -replace '\\', '/' + return "/mnt/$drive$rest" + } + return $WindowsPath -replace '\\', '/' +} + +# Main +Write-Header "Pre-Commit Validation (Windows)" +Write-Info "Level: $Level" +Write-Info "Repository: $RepoRoot" + +$bash = Find-BashExecutable +if (-not $bash) { + Write-Fail "Bash not found. Install WSL2 or Git for Windows." + exit 1 +} + +Write-Info "Using: $($bash.Type)" + +$scriptPath = Join-Path $ScriptDir "validate-before-commit.sh" +if (-not (Test-Path $scriptPath)) { + Write-Fail "Script not found: $scriptPath" + exit 1 +} + +$startTime = Get-Date + +try { + switch ($bash.Type) { + 'wsl' { + $unixScript = Convert-ToUnixPath $scriptPath + & wsl bash $unixScript $Level + } + 'gitbash' { + $unixScript = $scriptPath -replace '\\', '/' + & $bash.Path $unixScript $Level + } + } + $exitCode = $LASTEXITCODE +} +catch { + Write-Fail "Execution failed: $_" + $exitCode = 1 +} + +$duration = (Get-Date) - $startTime +$minutes = [math]::Floor($duration.TotalMinutes) +$seconds = $duration.Seconds + +Write-Header "Summary" +Write-Info "Duration: ${minutes}m ${seconds}s" + +if ($exitCode -eq 0) { + Write-Host "" + Write-ColoredOutput "=============================================" -Color Green + Write-ColoredOutput " ALL CHECKS PASSED - Ready to commit!" -Color Green + Write-ColoredOutput "=============================================" -Color Green + Write-Host "" + Write-Host "Next steps:" + Write-Host " git add -A" + Write-Host ' git commit -m "Your commit message"' + Write-Host "" +} else { + Write-Host "" + Write-ColoredOutput "=============================================" -Color Red + Write-ColoredOutput " VALIDATION FAILED - Do not commit!" -Color Red + Write-ColoredOutput "=============================================" -Color Red + Write-Host "" + Write-Host "Check the logs in: out/local-ci/logs/" + Write-Host "" +} + +exit $exitCode diff --git a/devops/scripts/validate-before-commit.sh b/devops/scripts/validate-before-commit.sh new file mode 100644 index 000000000..d6cc6b885 --- /dev/null +++ b/devops/scripts/validate-before-commit.sh @@ -0,0 +1,318 @@ +#!/usr/bin/env bash +# ============================================================================= +# PRE-COMMIT VALIDATION SCRIPT +# ============================================================================= +# Run this script before committing to ensure all CI checks will pass. +# +# Usage: +# ./devops/scripts/validate-before-commit.sh [level] +# +# Levels: +# quick - Smoke test only (~2 min) +# pr - Full PR-gating suite (~15 min) [default] +# full - All tests including extended (~45 min) +# +# Examples: +# ./devops/scripts/validate-before-commit.sh # PR-gating +# ./devops/scripts/validate-before-commit.sh quick # Smoke only +# ./devops/scripts/validate-before-commit.sh full # Everything +# +# ============================================================================= + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Validation level +LEVEL="${1:-pr}" + +# ============================================================================= +# UTILITIES +# ============================================================================= + +print_header() { + echo "" + echo -e "${CYAN}=============================================${NC}" + echo -e "${CYAN} $1${NC}" + echo -e "${CYAN}=============================================${NC}" + echo "" +} + +print_step() { + echo -e "${BLUE}>>> $1${NC}" +} + +print_success() { + echo -e "${GREEN}[PASS] $1${NC}" +} + +print_fail() { + echo -e "${RED}[FAIL] $1${NC}" +} + +print_warn() { + echo -e "${YELLOW}[WARN] $1${NC}" +} + +print_info() { + echo -e "${CYAN}[INFO] $1${NC}" +} + +# ============================================================================= +# CHECKS +# ============================================================================= + +check_git_status() { + print_step "Checking git status..." + + # Check for uncommitted changes + if ! git diff --quiet 2>/dev/null; then + print_warn "You have unstaged changes" + fi + + # Check for untracked files + local untracked + untracked=$(git ls-files --others --exclude-standard 2>/dev/null | wc -l) + if [[ "$untracked" -gt 0 ]]; then + print_warn "You have $untracked untracked file(s)" + fi + + # Show current branch + local branch + branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) + print_info "Current branch: $branch" +} + +check_dependencies() { + print_step "Checking dependencies..." + + local missing=0 + + # Check .NET + if ! command -v dotnet &>/dev/null; then + print_fail ".NET SDK not found" + missing=1 + else + local version + version=$(dotnet --version) + print_success ".NET SDK: $version" + fi + + # Check Docker + if ! command -v docker &>/dev/null; then + print_warn "Docker not found (some tests may fail)" + else + if docker info &>/dev/null; then + print_success "Docker: running" + else + print_warn "Docker: not running" + fi + fi + + # Check Git + if ! command -v git &>/dev/null; then + print_fail "Git not found" + missing=1 + else + print_success "Git: installed" + fi + + return $missing +} + +run_smoke_tests() { + print_step "Running smoke tests..." + + if "$SCRIPT_DIR/local-ci.sh" smoke; then + print_success "Smoke tests passed" + return 0 + else + print_fail "Smoke tests failed" + return 1 + fi +} + +run_pr_tests() { + print_step "Running PR-gating suite..." + + if "$SCRIPT_DIR/local-ci.sh" pr; then + print_success "PR-gating suite passed" + return 0 + else + print_fail "PR-gating suite failed" + return 1 + fi +} + +run_full_tests() { + print_step "Running full test suite..." + + if "$SCRIPT_DIR/local-ci.sh" full; then + print_success "Full test suite passed" + return 0 + else + print_fail "Full test suite failed" + return 1 + fi +} + +run_module_tests() { + print_step "Running module tests..." + + if "$SCRIPT_DIR/local-ci.sh" module; then + print_success "Module tests passed" + return 0 + else + print_fail "Module tests failed" + return 1 + fi +} + +validate_helm() { + if command -v helm &>/dev/null; then + print_step "Validating Helm chart..." + local chart="$REPO_ROOT/devops/helm/stellaops" + if [[ -d "$chart" ]]; then + if helm lint "$chart" &>/dev/null; then + print_success "Helm chart valid" + else + print_warn "Helm chart has warnings" + fi + fi + fi +} + +validate_compose() { + print_step "Validating Docker Compose..." + local compose="$REPO_ROOT/devops/compose/docker-compose.ci.yaml" + if [[ -f "$compose" ]]; then + if docker compose -f "$compose" config &>/dev/null; then + print_success "Docker Compose valid" + else + print_warn "Docker Compose has issues" + fi + fi +} + +# ============================================================================= +# MAIN +# ============================================================================= + +main() { + print_header "Pre-Commit Validation" + print_info "Level: $LEVEL" + print_info "Repository: $REPO_ROOT" + + local start_time + start_time=$(date +%s) + local failed=0 + + # Always run these checks + check_git_status + check_dependencies || failed=1 + + if [[ $failed -eq 1 ]]; then + print_fail "Dependency check failed" + exit 1 + fi + + # Run appropriate test level + case "$LEVEL" in + quick|smoke) + run_smoke_tests || failed=1 + ;; + pr|default) + run_smoke_tests || failed=1 + if [[ $failed -eq 0 ]]; then + run_module_tests || failed=1 + fi + if [[ $failed -eq 0 ]]; then + run_pr_tests || failed=1 + fi + validate_helm + validate_compose + ;; + full|all) + run_smoke_tests || failed=1 + if [[ $failed -eq 0 ]]; then + run_full_tests || failed=1 + fi + validate_helm + validate_compose + ;; + *) + print_fail "Unknown level: $LEVEL" + echo "Valid levels: quick, pr, full" + exit 1 + ;; + esac + + # Calculate duration + local end_time + end_time=$(date +%s) + local duration=$((end_time - start_time)) + local minutes=$((duration / 60)) + local seconds=$((duration % 60)) + + # Final summary + print_header "Summary" + print_info "Duration: ${minutes}m ${seconds}s" + + if [[ $failed -eq 0 ]]; then + echo "" + echo -e "${GREEN}=============================================${NC}" + echo -e "${GREEN} ALL CHECKS PASSED - Ready to commit!${NC}" + echo -e "${GREEN}=============================================${NC}" + echo "" + echo "Next steps:" + echo " git add -A" + echo " git commit -m \"Your commit message\"" + echo "" + exit 0 + else + echo "" + echo -e "${RED}=============================================${NC}" + echo -e "${RED} VALIDATION FAILED - Do not commit!${NC}" + echo -e "${RED}=============================================${NC}" + echo "" + echo "Check the logs in: out/local-ci/logs/" + echo "" + exit 1 + fi +} + +# Show usage if --help +if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then + cat <