docs re-org, audit fixes, build fixes
This commit is contained in:
11
CLAUDE.md
11
CLAUDE.md
@@ -656,7 +656,7 @@ Always update task status in `docs/implplan/SPRINT_*.md`:
|
||||
|
||||
Before coding, confirm required docs are read:
|
||||
- `docs/README.md`
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- Relevant module dossier (e.g., `docs/modules/<module>/architecture.md`)
|
||||
- Module-specific `AGENTS.md` file
|
||||
@@ -674,13 +674,14 @@ Before coding, confirm required docs are read:
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Architecture overview:** `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- **Architecture overview:** `docs/ARCHITECTURE_OVERVIEW.md`
|
||||
- **Architecture reference:** `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- **Module dossiers:** `docs/modules/<module>/architecture.md`
|
||||
- **Database specification:** `docs/db/SPECIFICATION.md`
|
||||
- **PostgreSQL operations:** `docs/operations/postgresql-guide.md`
|
||||
- **API/CLI reference:** `docs/09_API_CLI_REFERENCE.md`
|
||||
- **Offline operation:** `docs/24_OFFLINE_KIT.md`
|
||||
- **Quickstart:** `docs/10_CONCELIER_CLI_QUICKSTART.md`
|
||||
- **API/CLI reference:** `docs/API_CLI_REFERENCE.md`
|
||||
- **Offline operation:** `docs/OFFLINE_KIT.md`
|
||||
- **Quickstart:** `docs/CONCELIER_CLI_QUICKSTART.md`
|
||||
- **Sprint planning:** `docs/implplan/SPRINT_*.md`
|
||||
|
||||
## CI/CD
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- Only documentation and fixture assets live here; code changes belong to module repos and must be coordinated via the owning sprint.
|
||||
|
||||
## Required Reading (treat as read before DOING)
|
||||
- `docs/README.md` and `docs/07_HIGH_LEVEL_ARCHITECTURE.md`.
|
||||
- `docs/README.md` and `docs/ARCHITECTURE_REFERENCE.md`.
|
||||
- Module dossiers relevant to the document being edited (e.g., `docs/modules/advisory-ai/architecture.md`, `docs/modules/ui/architecture.md`, `docs/modules/airgap/architecture.md`, `docs/modules/platform/architecture-overview.md`).
|
||||
- Active sprint file: `docs/implplan/SPRINT_0301_0001_0001_docs_md_i.md` (Docs Tasks Md.I).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
This document is the 10-minute tour for StellaOps: what components exist, how they fit together, and what "offline-first + deterministic + evidence-linked decisions" means in practice.
|
||||
|
||||
For the full reference map (services, boundaries, detailed flows), see `docs/07_HIGH_LEVEL_ARCHITECTURE.md`.
|
||||
For the full reference map (services, boundaries, detailed flows), see `docs/ARCHITECTURE_REFERENCE.md`.
|
||||
|
||||
## Guiding Principles
|
||||
|
||||
@@ -74,7 +74,7 @@ At a high level, StellaOps is a set of services grouped by responsibility:
|
||||
|
||||
## References
|
||||
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/24_OFFLINE_KIT.md`
|
||||
- `docs/09_API_CLI_REFERENCE.md`
|
||||
- `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- `docs/OFFLINE_KIT.md`
|
||||
- `docs/API_CLI_REFERENCE.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
@@ -3,7 +3,7 @@
|
||||
This document is the canonical index for StellaOps architecture.
|
||||
It is intentionally a map, not a full re-statement of every module dossier.
|
||||
|
||||
If you want a short walkthrough, start with `docs/40_ARCHITECTURE_OVERVIEW.md`.
|
||||
If you want a short walkthrough, start with `docs/ARCHITECTURE_OVERVIEW.md`.
|
||||
|
||||
## How the docs are organized
|
||||
|
||||
@@ -90,7 +90,7 @@ Tenancy and identity context are part of the platform contract:
|
||||
## APIs and CLI reference
|
||||
|
||||
Canonical entry points:
|
||||
- API and CLI reference hub: `docs/09_API_CLI_REFERENCE.md`
|
||||
- API and CLI reference hub: `docs/API_CLI_REFERENCE.md`
|
||||
- API conventions (headers, errors, pagination, determinism): `docs/api/overview.md`
|
||||
- API contracts and samples: `docs/api/`
|
||||
- CLI command guides: `docs/modules/cli/guides/commands/`
|
||||
@@ -98,15 +98,15 @@ Canonical entry points:
|
||||
## Offline, verification, and operations
|
||||
|
||||
Canonical entry points:
|
||||
- Offline Kit: `docs/24_OFFLINE_KIT.md`
|
||||
- Security hardening: `docs/17_SECURITY_HARDENING_GUIDE.md`
|
||||
- Installation guide: `docs/21_INSTALL_GUIDE.md`
|
||||
- Offline Kit: `docs/OFFLINE_KIT.md`
|
||||
- Security hardening: `docs/SECURITY_HARDENING_GUIDE.md`
|
||||
- Installation guide: `docs/INSTALL_GUIDE.md`
|
||||
- Ops and runbooks: `docs/operations/`, `docs/modules/*/operations/`
|
||||
|
||||
## Data and schemas
|
||||
|
||||
Use these as the canonical map for schemas and contracts:
|
||||
- Data schemas (high-level index): `docs/11_DATA_SCHEMAS.md`
|
||||
- Data schemas (high-level index): `docs/DATA_SCHEMAS.md`
|
||||
- Database specifications: `docs/db/`
|
||||
- Events (schemas + samples): `docs/events/`
|
||||
|
||||
@@ -114,5 +114,5 @@ Use these as the canonical map for schemas and contracts:
|
||||
|
||||
- Product overview: `docs/overview.md`
|
||||
- Key features: `docs/key-features.md`
|
||||
- Roadmap (internal): `docs/05_ROADMAP.md`
|
||||
- Glossary: `docs/14_GLOSSARY_OF_TERMS.md`
|
||||
- Roadmap (internal): `docs/ROADMAP.md`
|
||||
- Glossary: `docs/GLOSSARY.md`
|
||||
@@ -8,8 +8,8 @@ This quickstart gets an operator to a working advisory ingestion loop:
|
||||
This document stays high level and defers detailed configuration and connector behavior to the Concelier module dossier.
|
||||
|
||||
## 1) Prerequisites
|
||||
- Deployment: follow `docs/21_INSTALL_GUIDE.md` (Compose profiles under `deploy/compose/`).
|
||||
- Offline/air-gap: follow `docs/24_OFFLINE_KIT.md` and `docs/airgap/overview.md`.
|
||||
- Deployment: follow `docs/INSTALL_GUIDE.md` (Compose profiles under `deploy/compose/`).
|
||||
- Offline/air-gap: follow `docs/OFFLINE_KIT.md` and `docs/airgap/overview.md`.
|
||||
- Local dev (optional): .NET SDK version pinned by `global.json`.
|
||||
|
||||
## 2) Run Concelier
|
||||
@@ -18,7 +18,7 @@ This document stays high level and defers detailed configuration and connector b
|
||||
Use the deterministic Compose profiles under `deploy/compose/` and enable Concelier in the selected profile.
|
||||
|
||||
Start here:
|
||||
- `docs/21_INSTALL_GUIDE.md`
|
||||
- `docs/INSTALL_GUIDE.md`
|
||||
- `docs/modules/concelier/operations/`
|
||||
|
||||
### Option B: Run the service from source (dev/debug)
|
||||
@@ -99,4 +99,4 @@ For read-only inspection (list/get/export), use:
|
||||
- Concelier module dossier: `docs/modules/concelier/README.md`
|
||||
- Concelier operations: `docs/modules/concelier/operations/`
|
||||
- CLI command guides: `docs/modules/cli/guides/commands/`
|
||||
- API + CLI reference index: `docs/09_API_CLI_REFERENCE.md`
|
||||
- API + CLI reference index: `docs/API_CLI_REFERENCE.md`
|
||||
@@ -20,8 +20,8 @@
|
||||
StellaOps is a deterministic, offline-first SBOM + VEX platform built as a microservice architecture. The system is designed so every verdict can be replayed from concrete evidence (SBOM slices, advisory/VEX observations, policy decision traces, and optional attestations).
|
||||
|
||||
### Canonical references
|
||||
- Architecture overview (10-minute tour): `docs/40_ARCHITECTURE_OVERVIEW.md`
|
||||
- High-level reference map: `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- Architecture overview (10-minute tour): `docs/ARCHITECTURE_OVERVIEW.md`
|
||||
- High-level reference map: `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- Detailed architecture index: `docs/technical/architecture/README.md`
|
||||
- Topology: `docs/technical/architecture/platform-topology.md`
|
||||
- Infrastructure: `docs/technical/architecture/infrastructure-dependencies.md`
|
||||
@@ -170,7 +170,7 @@ Canonical guide:
|
||||
|
||||
Related references:
|
||||
- Compose profiles: `deploy/compose/README.md`
|
||||
- Install guide: `docs/21_INSTALL_GUIDE.md`
|
||||
- Install guide: `docs/INSTALL_GUIDE.md`
|
||||
- Service-specific runbooks: `docs/modules/<module>/operations/`
|
||||
## Service-by-Service Debugging Guide
|
||||
|
||||
@@ -711,10 +711,10 @@ sudo docker compose -f docker-compose.dev.yaml up -d
|
||||
|
||||
### Key Documentation
|
||||
|
||||
- **Architecture:** `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- **Architecture:** `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- **Build Commands:** `CLAUDE.md`
|
||||
- **Database Spec:** `docs/db/SPECIFICATION.md`
|
||||
- **API Reference:** `docs/09_API_CLI_REFERENCE.md`
|
||||
- **API Reference:** `docs/API_CLI_REFERENCE.md`
|
||||
- **Module Architecture:** `docs/modules/<module>/architecture.md`
|
||||
|
||||
### Support
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
| --- | --- |
|
||||
| What is StellaOps? | A sovereign, offline-first container-security platform focused on deterministic, replayable evidence: SBOMs, advisories, VEX, policy decisions, and attestations bound to image digests. |
|
||||
| What makes it "deterministic"? | The same inputs produce the same outputs (stable ordering, stable IDs, replayable artifacts). Determinism is treated as a product feature and enforced by tests and fixtures. |
|
||||
| Does it run fully offline? | Yes. Offline operation is a first-class workflow (bundles, mirrors, importer/controller). See `docs/24_OFFLINE_KIT.md` and `docs/airgap/overview.md`. |
|
||||
| Does it run fully offline? | Yes. Offline operation is a first-class workflow (bundles, mirrors, importer/controller). See `docs/OFFLINE_KIT.md` and `docs/airgap/overview.md`. |
|
||||
| Which formats are supported? | SBOMs: SPDX 3.0.1 and CycloneDX 1.7 (1.6 backward compatible). VEX: OpenVEX-first decisioning with issuer trust and consensus. Attestations: in-toto/DSSE where enabled. |
|
||||
| How do I deploy it? | Use deterministic bundles under `deploy/` (Compose/Helm) with digests sourced from `deploy/releases/`. Start with `docs/21_INSTALL_GUIDE.md`. |
|
||||
| How do I deploy it? | Use deterministic bundles under `deploy/` (Compose/Helm) with digests sourced from `deploy/releases/`. Start with `docs/INSTALL_GUIDE.md`. |
|
||||
| How do policy gates work? | Policy combines VEX-first inputs with lattice/precedence rules so outcomes are stable and explainable. See `docs/policy/vex-trust-model.md`. |
|
||||
| Is multi-tenancy supported? | Yes; tenancy boundaries and roles/scopes are documented and designed to support regulated environments. See `docs/security/tenancy-overview.md` and `docs/security/scopes-and-roles.md`. |
|
||||
| Can I extend it? | Yes: connectors, plugins, and policy packs are designed to be composable without losing determinism. Start with module dossiers under `docs/modules/`. |
|
||||
| Where is the roadmap? | `docs/05_ROADMAP.md` (priority bands + definition of "done"). |
|
||||
| Where is the roadmap? | `docs/ROADMAP.md` (priority bands + definition of "done"). |
|
||||
| Where do I find deeper docs? | `docs/technical/README.md` is the detailed index; `docs/modules/` contains per-module dossiers. |
|
||||
|
||||
## Further reading
|
||||
- Vision: `docs/03_VISION.md`
|
||||
- Feature matrix: `docs/04_FEATURE_MATRIX.md`
|
||||
- Architecture overview: `docs/40_ARCHITECTURE_OVERVIEW.md`
|
||||
- High-level architecture: `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- Offline kit: `docs/24_OFFLINE_KIT.md`
|
||||
- Install guide: `docs/21_INSTALL_GUIDE.md`
|
||||
- Vision: `docs/VISION.md`
|
||||
- Feature matrix: `docs/FEATURE_MATRIX.md`
|
||||
- Architecture overview: `docs/ARCHITECTURE_OVERVIEW.md`
|
||||
- High-level architecture: `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- Offline kit: `docs/OFFLINE_KIT.md`
|
||||
- Install guide: `docs/INSTALL_GUIDE.md`
|
||||
- Quickstart: `docs/quickstart.md`
|
||||
@@ -49,7 +49,7 @@ Approval is recorded via Git forge review or a signed commit trailer
|
||||
|
||||
* Every tag is **co‑signed by at least one Security Maintainer**.
|
||||
* CI emits a **signed SPDX SBOM** + **Cosign provenance**.
|
||||
* Release cadence is fixed – see [Release Engineering Playbook](13_RELEASE_ENGINEERING_PLAYBOOK.md).
|
||||
* Release cadence is fixed – see [Release Engineering Playbook](RELEASE_ENGINEERING_PLAYBOOK.md).
|
||||
* Security fixes may create out‑of‑band `x.y.z‑hotfix` tags.
|
||||
|
||||
---
|
||||
@@ -59,8 +59,8 @@ Approval is recorded via Git forge review or a signed commit trailer
|
||||
| Situation | Escalation |
|
||||
|-----------|------------|
|
||||
| Technical deadlock | **Maintainer Summit** (recorded & published) |
|
||||
| Security bug | Follow [Security Policy](13_SECURITY_POLICY.md) |
|
||||
| Code of Conduct violation | See `12_CODE_OF_CONDUCT.md` escalation ladder |
|
||||
| Security bug | Follow [Security Policy](SECURITY_POLICY.md) |
|
||||
| Code of Conduct violation | See `CODE_OF_CONDUCT.md` escalation ladder |
|
||||
|
||||
---
|
||||
|
||||
@@ -4,7 +4,7 @@ StellaOps is a deterministic, offline-first container security platform: every v
|
||||
|
||||
## Two Levels of Documentation
|
||||
|
||||
- **High-level (canonical):** the curated guides in `docs/*.md` (usually numbered).
|
||||
- **High-level (canonical):** the curated guides in `docs/*.md`.
|
||||
- **Detailed (reference):** deep dives under `docs/**` (module dossiers, architecture notes, API contracts/samples, runbooks, schemas). The entry point is `docs/technical/README.md`.
|
||||
|
||||
This documentation set is internal and does not keep compatibility stubs for old paths. Content is consolidated to reduce duplication and outdated pages.
|
||||
@@ -13,23 +13,23 @@ This documentation set is internal and does not keep compatibility stubs for old
|
||||
|
||||
| Goal | Open this |
|
||||
| --- | --- |
|
||||
| Understand the product in 2 minutes | [overview.md](/docs/overview/) |
|
||||
| Run a first scan (CLI) | [quickstart.md](/docs/quickstart/) |
|
||||
| Browse capabilities | [key-features.md](/docs/key-features/) |
|
||||
| Roadmap (priorities + definition of "done") | [05_ROADMAP.md](/docs/05_roadmap/) |
|
||||
| Architecture: high-level overview | [40_ARCHITECTURE_OVERVIEW.md](/docs/40_architecture_overview/) |
|
||||
| Architecture: full reference map | [07_HIGH_LEVEL_ARCHITECTURE.md](/docs/07_high_level_architecture/) |
|
||||
| Architecture: user flows (UML) | [technical/architecture/user-flows.md](/docs/technical/architecture/user-flows/) |
|
||||
| Architecture: module matrix (46 modules) | [technical/architecture/module-matrix.md](/docs/technical/architecture/module-matrix/) |
|
||||
| Architecture: data flows | [technical/architecture/data-flows.md](/docs/technical/architecture/data-flows/) |
|
||||
| Architecture: schema mapping | [technical/architecture/schema-mapping.md](/docs/technical/architecture/schema-mapping/) |
|
||||
| Offline / air-gap operations | [24_OFFLINE_KIT.md](/docs/24_offline_kit/) |
|
||||
| Security deployment hardening | [17_SECURITY_HARDENING_GUIDE.md](/docs/17_security_hardening_guide/) |
|
||||
| Ingest advisories (Concelier + CLI) | [10_CONCELIER_CLI_QUICKSTART.md](/docs/10_concelier_cli_quickstart/) |
|
||||
| Develop plugins/connectors | [10_PLUGIN_SDK_GUIDE.md](/docs/10_plugin_sdk_guide/) |
|
||||
| Console (Web UI) operator guide | [15_UI_GUIDE.md](/docs/15_ui_guide/) |
|
||||
| VEX consensus and issuer trust | [16_VEX_CONSENSUS_GUIDE.md](/docs/16_vex_consensus_guide/) |
|
||||
| Vulnerability Explorer guide | [20_VULNERABILITY_EXPLORER_GUIDE.md](/docs/20_vulnerability_explorer_guide/) |
|
||||
| Understand the product in 2 minutes | [overview.md](overview.md) |
|
||||
| Run a first scan (CLI) | [quickstart.md](quickstart.md) |
|
||||
| Browse capabilities | [key-features.md](key-features.md) |
|
||||
| Roadmap (priorities + definition of "done") | [ROADMAP.md](ROADMAP.md) |
|
||||
| Architecture: high-level overview | [ARCHITECTURE_OVERVIEW.md](ARCHITECTURE_OVERVIEW.md) |
|
||||
| Architecture: full reference map | [ARCHITECTURE_REFERENCE.md](ARCHITECTURE_REFERENCE.md) |
|
||||
| Architecture: user flows (UML) | [technical/architecture/user-flows.md](technical/architecture/user-flows.md) |
|
||||
| Architecture: module matrix (46 modules) | [technical/architecture/module-matrix.md](technical/architecture/module-matrix.md) |
|
||||
| Architecture: data flows | [technical/architecture/data-flows.md](technical/architecture/data-flows.md) |
|
||||
| Architecture: schema mapping | [technical/architecture/schema-mapping.md](technical/architecture/schema-mapping.md) |
|
||||
| Offline / air-gap operations | [OFFLINE_KIT.md](OFFLINE_KIT.md) |
|
||||
| Security deployment hardening | [SECURITY_HARDENING_GUIDE.md](SECURITY_HARDENING_GUIDE.md) |
|
||||
| Ingest advisories (Concelier + CLI) | [CONCELIER_CLI_QUICKSTART.md](CONCELIER_CLI_QUICKSTART.md) |
|
||||
| Develop plugins/connectors | [PLUGIN_SDK_GUIDE.md](PLUGIN_SDK_GUIDE.md) |
|
||||
| Console (Web UI) operator guide | [UI_GUIDE.md](UI_GUIDE.md) |
|
||||
| VEX consensus and issuer trust | [VEX_CONSENSUS_GUIDE.md](VEX_CONSENSUS_GUIDE.md) |
|
||||
| Vulnerability Explorer guide | [VULNERABILITY_EXPLORER_GUIDE.md](VULNERABILITY_EXPLORER_GUIDE.md) |
|
||||
|
||||
## Detailed Indexes
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ flowchart LR
|
||||
|
||||
1. Extend `scripts/dev-test.sh` so local contributors get the layer by default.
|
||||
2. Add a dedicated workflow in `.gitea/workflows/` (or GitLab job in `.gitlab-ci.yml`).
|
||||
3. Register the job in `docs/19_TEST_SUITE_OVERVIEW.md` *and* list its metric
|
||||
3. Register the job in `docs/TEST_SUITE_OVERVIEW.md` *and* list its metric
|
||||
in `docs/metrics/README.md`.
|
||||
4. If the test requires network isolation, inherit from `NetworkIsolatedTestBase`.
|
||||
5. If the test uses golden corpus, add cases to `bench/golden-corpus/`.
|
||||
@@ -251,11 +251,12 @@ flowchart LR
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Sprint Epic 5100 - Testing Strategy](implplan/SPRINT_5100_0000_0000_epic_summary.md)
|
||||
- [Testing Strategy Models](testing/testing-strategy-models.md)
|
||||
- [Test Catalog](testing/TEST_CATALOG.yml)
|
||||
- [Testing README](testing/README.md) - Complete testing documentation index
|
||||
- [CI/CD Test Strategy](cicd/test-strategy.md) - CI/CD integration details
|
||||
- [tests/AGENTS.md](../tests/AGENTS.md)
|
||||
- [Offline Operation Guide](24_OFFLINE_KIT.md)
|
||||
- [Offline Operation Guide](OFFLINE_KIT.md)
|
||||
- [Module Architecture Dossiers](modules/)
|
||||
|
||||
---
|
||||
@@ -325,7 +325,7 @@ Before submitting a PR that involves digests or attestations:
|
||||
|
||||
- [docs/testing/schemas/determinism-manifest.schema.json](../testing/schemas/determinism-manifest.schema.json) - JSON Schema for manifests
|
||||
- [docs/modules/policy/design/policy-determinism-tests.md](../modules/policy/design/policy-determinism-tests.md) - Policy engine determinism
|
||||
- [docs/19_TEST_SUITE_OVERVIEW.md](../19_TEST_SUITE_OVERVIEW.md) - Testing strategy
|
||||
- [docs/TEST_SUITE_OVERVIEW.md](../TEST_SUITE_OVERVIEW.md) - Testing strategy
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **Comprehensive table of every capability in the platform.**
|
||||
>
|
||||
> For competitive differentiation highlights, see [`key-features.md`](key-features.md).
|
||||
> For tier-based pricing details, see [`04_FEATURE_MATRIX.md`](04_FEATURE_MATRIX.md).
|
||||
> For tier-based pricing details, see [`FEATURE_MATRIX.md`](FEATURE_MATRIX.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -142,6 +142,16 @@ services.AddSingleton<IGuidProvider, SystemGuidProvider>();
|
||||
| 2026-01-04 | DET-019 complete: Scanner.WebService refactored - 12 endpoint/service files (EpssEndpoints, EvidenceEndpoints, SmartDiffEndpoints, UnknownsEndpoints, WitnessEndpoints, TriageInboxEndpoints, ProofBundleEndpoints, ReportSigner, ScoreReplayService, TestManifestRepository, SliceQueryService, UnifiedEvidenceService) plus dependency fixes in Scanner.Sources (SourceTriggerDispatcher, SourceContracts) and Scanner.WebService (EvidenceBundleExporter, GatingReasonService). All builds verified. | Agent |
|
||||
| 2026-01-04 | DET-020 in progress: Scanner.Analyzers.Native hardening extractors refactored - ElfHardeningExtractor, MachoHardeningExtractor, PeHardeningExtractor with TimeProvider injection. OfflineBuildIdIndex refactored. Build verified. RuntimeCapture adapters (LinuxEbpfCaptureAdapter, MacOsDyldCaptureAdapter, WindowsEtwCaptureAdapter) pending - require TimeProvider and IGuidProvider injection for 18+ usages across eBPF/DYLD/ETW tracing. | Agent |
|
||||
| 2026-01-04 | DET-020 complete: RuntimeCapture adapters refactored - LinuxEbpfCaptureAdapter, MacOsDyldCaptureAdapter, WindowsEtwCaptureAdapter with TimeProvider and IGuidProvider injection (SessionId, StartTime, EndTime, Timestamp fields). RuntimeEvidenceAggregator.MergeWithStaticAnalysis updated with optional TimeProvider parameter. StackTraceCapture.CollapsedStack.Parse updated with optional TimeProvider parameter. Added StellaOps.Determinism.Abstractions reference to project. All builds verified. | Agent |
|
||||
| 2026-01-06 | DET-021(d) continued: Cryptography.Kms module refactored - AwsKmsClient, GcpKmsClient, FileKmsClient (6 usages), Pkcs11KmsClient, Pkcs11Facade, GcpKmsFacade, AwsKmsFacade, Fido2KmsClient, Fido2Options with TimeProvider injection. Removed unnecessary TimeProvider.Abstractions package (built into .NET 10). All builds verified. | Agent |
|
||||
| 2026-01-06 | DET-021 continued: SbomService module refactored - Clock.cs (SystemClock delegates to TimeProvider), LineageGraphService, SbomLineageEdgeRepository, PostgresOrchestratorRepository, InMemoryOrchestratorRepository, ReplayVerificationService, LineageCompareService, LineageExportService, LineageHoverCache, RegistrySourceService, OrchestratorControlService, WatermarkService. DTOs changed from default timestamps to required fields. All builds verified. | Agent |
|
||||
| 2026-01-06 | DET-021 continued: Findings module refactored - LedgerEventMapping (TimeProvider parameter), Program.cs (TimeProvider injection), EvidenceGraphBuilder (TimeProvider constructor). Fixed pre-existing null reference issue in FindingWorkflowService.cs. All builds verified. | Agent |
|
||||
| 2026-01-06 | DET-021 continued: Notify module refactored - InMemoryRepositories.cs (15 repository adapters: Channel, Rule, Template, Delivery, Digest, Lock, EscalationPolicy, EscalationState, OnCallSchedule, QuietHours, MaintenanceWindow, Inbox with TimeProvider constructors). All builds verified. | Agent |
|
||||
| 2026-01-06 | DET-021 continued: ExportCenter module refactored - LineageEvidencePackService (12 usages), ExportRetentionService (1 usage), InMemorySchedulingStores (1 usage), ExportVerificationModels (VerifiedAt made required), ExportVerificationService (TimeProvider constructor + Failed factory calls), ExceptionReportGenerator (4 usages). All builds verified. | Agent |
|
||||
| 2026-01-07 | DET-021 continued: Orchestrator module refactored - Infrastructure/Postgres repositories (PostgresPackRunRepository, PostgresPackRegistryRepository, PostgresQuotaRepository, PostgresRunRepository, PostgresSourceRepository, PostgresThrottleRepository, PostgresWatermarkRepository with TimeProvider constructors and usage updates). WebService/Endpoints (HealthEndpoints, KpiEndpoints with TimeProvider injection via [FromServices]). Domain records (IBackfillRepository/BackfillCheckpoint.Create/Complete/Fail methods now accept timestamps). All DateTimeOffset.UtcNow usages in production Postgres/Endpoint code eliminated. Remaining: CLI module (~100 usages), Policy.Gateway module (~50 usages). | Agent |
|
||||
| 2026-01-07 | DET-021 continued: CLI module critical verifiers refactored - ForensicVerifier.cs (TimeProvider constructor, 2 usages updated), ImageAttestationVerifier.cs (TimeProvider constructor, 7 usages updated for verification timestamps and max age checks). Note: Pre-existing build errors in Policy.Tools and Scanner.Analyzers.Lang.Python unrelated to determinism changes. Further CLI refactoring deferred - large scope (~90+ remaining usages across 30+ files in short-lived CLI process). | Agent |
|
||||
| 2026-01-07 | DET-021 continued: Policy.Gateway module refactored - ExceptionEndpoints.cs (10 DateTimeOffset.UtcNow usages across 6 endpoints: POST, PUT, approve, activate, extend, revoke), GateEndpoints.cs (3 usages: evaluate endpoint + health check), GovernanceEndpoints.cs (9 usages across sealed mode + risk profile handlers, plus RecordAudit helper), RegistryWebhookEndpoints.cs (3 usages: Docker, Harbor, generic webhook handlers), ExceptionApprovalEndpoints.cs (2 usages: CreateApprovalRequestAsync), InMemoryGateEvaluationQueue.cs (constructor + 2 usages). All handlers now use TimeProvider via [FromServices] or constructor injection. Note: InitializeDefaultProfiles() static initializer retained DateTimeOffset.UtcNow for bootstrap/seed data - acceptable for one-time startup code. | Agent |
|
||||
| 2026-01-07 | DET-021 continued: Policy.Registry module refactored - InMemoryPolicyPackStore.cs (TimeProvider constructor, 4 usages: CreateAsync, UpdateAsync, UpdateStatusAsync, AddHistoryEntry), InMemorySnapshotStore.cs (TimeProvider constructor, 1 usage), InMemoryVerificationPolicyStore.cs (TimeProvider constructor, 2 usages: CreateAsync, UpdateAsync), InMemoryOverrideStore.cs (TimeProvider constructor, 2 usages: CreateAsync, ApproveAsync), InMemoryViolationStore.cs (TimeProvider constructor, 2 usages: AppendAsync, AppendBatchAsync). All builds verified. | Agent |
|
||||
| 2026-01-07 | DET-021 continued: Policy.Engine module refactored - InMemoryExceptionRepository.cs (TimeProvider constructor, 2 usages: RevokeAsync, ExpireAsync), InMemoryPolicyPackRepository.cs (TimeProvider constructor, 6 usages across CreateAsync, UpsertRevisionAsync, StoreBundleAsync). Remaining Policy.Engine usages in domain models (TenantContextModels, EvidenceBundle, ExceptionMapper), telemetry services (MigrationTelemetryService, EwsTelemetryService), and complex services (PoEValidationService, PolicyMergePreviewService, VerdictLinkService, RiskProfileConfigurationService) require additional pattern decisions - some are default property initializers requiring schema-level changes. All modified files build verified. | Agent |
|
||||
|
||||
## Decisions & Risks
|
||||
- **Decision:** Defer determinism refactoring from MAINT audit to dedicated sprint for focused, systematic approach.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **Audience:** Deployment Guild, Console Guild, platform operators.
|
||||
> **Scope:** Acquire the `stellaops/web-ui` image, run it with Compose or Helm, mirror it for air‑gapped environments, and keep parity with CLI workflows.
|
||||
|
||||
This guide focuses on the new **StellaOps Console** container. Start with the general [Installation Guide](../21_INSTALL_GUIDE.md) for shared prerequisites (Docker, registry access, TLS) and use the steps below to layer in the console.
|
||||
This guide focuses on the new **StellaOps Console** container. Start with the general [Installation Guide](../INSTALL_GUIDE.md) for shared prerequisites (Docker, registry access, TLS) and use the steps below to layer in the console.
|
||||
|
||||
---
|
||||
|
||||
@@ -205,7 +205,7 @@ Track progress for the CLI commands via `DOCS-CONSOLE-23-014` (CLI vs UI parity
|
||||
- `deploy/helm/stellaops/values-*.yaml` – Helm defaults per environment.
|
||||
- `/docs/deploy/console.md` – Detailed environment variables, CSP, health checks.
|
||||
- `/docs/security/console-security.md` – Auth flows, scopes, DPoP, monitoring.
|
||||
- `docs/15_UI_GUIDE.md` – Console workflows and offline posture.
|
||||
- `docs/UI_GUIDE.md` – Console workflows and offline posture.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Core Thesis:** Stella Ops isn't a scanner that outputs findings. It's a platform that outputs **attestable decisions that can be replayed**. That difference survives auditors, regulators, and supply-chain propagation.
|
||||
|
||||
> **Looking for the complete feature catalog?** See [`full-features-list.md`](full-features-list.md) for the comprehensive list of all platform capabilities, or [`04_FEATURE_MATRIX.md`](04_FEATURE_MATRIX.md) for tier-by-tier availability.
|
||||
> **Looking for the complete feature catalog?** See [`full-features-list.md`](full-features-list.md) for the comprehensive list of all platform capabilities, or [`FEATURE_MATRIX.md`](FEATURE_MATRIX.md) for tier-by-tier availability.
|
||||
|
||||
---
|
||||
|
||||
@@ -266,7 +266,7 @@ Layer 3 (Runtime): eBPF probe confirms function was actually executed
|
||||
|
||||
**Why it matters:** Regression-proof audits. Evidence, not assumptions, drives releases.
|
||||
|
||||
**Reference:** `docs/testing/testing-strategy-models.md`, `docs/19_TEST_SUITE_OVERVIEW.md`
|
||||
**Reference:** `docs/testing/testing-strategy-models.md`, `docs/TEST_SUITE_OVERVIEW.md`
|
||||
|
||||
---
|
||||
|
||||
@@ -299,6 +299,6 @@ stella scan --offline --image <digest>
|
||||
- **Competitive Landscape**: `docs/market/competitive-landscape.md`
|
||||
- **Moat Strategy**: `docs/market/moat-strategy-summary.md`
|
||||
- **Proof Architecture**: `docs/modules/platform/proof-driven-moats-architecture.md`
|
||||
- **Vision**: `docs/03_VISION.md`
|
||||
- **Architecture Overview**: `docs/40_ARCHITECTURE_OVERVIEW.md`
|
||||
- **Vision**: `docs/VISION.md`
|
||||
- **Architecture Overview**: `docs/ARCHITECTURE_OVERVIEW.md`
|
||||
- **Quickstart**: `docs/quickstart.md`
|
||||
|
||||
@@ -132,8 +132,8 @@ This isn't a feature gap—it's a category difference. Retrofitting it requires:
|
||||
- Engineering: ensure new features keep determinism + sovereign crypto front-and-center; link reachability attestations into proof graph.
|
||||
|
||||
## Cross-links
|
||||
- Vision: `docs/03_VISION.md` (Moats section)
|
||||
- Architecture: `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- Vision: `docs/VISION.md` (Moats section)
|
||||
- Architecture: `docs/ARCHITECTURE_REFERENCE.md`
|
||||
- Reachability moat details: `docs/reachability/lead.md`
|
||||
- Source advisory: `docs/product-advisories/23-Nov-2025 - Stella Ops vs Competitors.md`
|
||||
- **Claims Citation Index**: [`docs/market/claims-citation-index.md`](claims-citation-index.md)
|
||||
|
||||
@@ -383,7 +383,7 @@ Produce **entrypoint‑aware differential SBOMs** and continually **re‑enrich*
|
||||
{
|
||||
"schema":"cyclonedx+stella-diff@1.0",
|
||||
"base_sbom":"sha256:...",
|
||||
"entrypoint": ["/app/bin/Release/net8.0/app.dll"],
|
||||
"entrypoint": ["/app/bin/Release/net10.0/app.dll"],
|
||||
"cmd": ["--urls","http://127.0.0.1:8080"],
|
||||
"static_reachable": ["pkg:nuget/Kestrel@*", "pkg:nuget/System.Data@*"],
|
||||
"runtime_observed": ["pkg:rpm/openssl@3.0.9"],
|
||||
|
||||
57
docs/modules/_template/AGENTS.md
Normal file
57
docs/modules/_template/AGENTS.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# <Module Name> - AI Agent Instructions
|
||||
|
||||
> Instructions for AI agents (Claude Code, GitHub Copilot, etc.) working on this module.
|
||||
|
||||
## Module Context
|
||||
|
||||
- **Primary Language**: C# (.NET 10)
|
||||
- **Project Type**: Library / WebService / Worker
|
||||
- **Test Framework**: xUnit with Testcontainers
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/<Module>/__Libraries/StellaOps.<Module>.Core/` | Core business logic |
|
||||
| `src/<Module>/StellaOps.<Module>.WebService/Program.cs` | Service entry point |
|
||||
| `src/<Module>/__Tests/` | Test projects |
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding a New Feature
|
||||
1. Start with the interface in `*.Core`
|
||||
2. Implement in the appropriate layer
|
||||
3. Add tests in the corresponding `*.Tests` project
|
||||
4. Update API contracts if WebService
|
||||
|
||||
### Fixing a Bug
|
||||
1. Write a failing test first
|
||||
2. Fix the implementation
|
||||
3. Verify all related tests pass
|
||||
|
||||
## Patterns to Follow
|
||||
|
||||
- **Dependency Injection**: All dependencies via constructor injection
|
||||
- **Async/Await**: Propagate CancellationToken through all async chains
|
||||
- **Error Handling**: Use Result<T> pattern, not exceptions for expected failures
|
||||
- **Logging**: Structured logging with semantic properties
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
- Direct `new HttpClient()` - use IHttpClientFactory
|
||||
- `DateTime.UtcNow` - use TimeProvider injection
|
||||
- `Guid.NewGuid()` - use IGuidGenerator injection
|
||||
- Culture-sensitive string operations - use InvariantCulture
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
- Unit tests for all public APIs
|
||||
- Integration tests for database operations
|
||||
- Snapshot tests for serialization
|
||||
- All tests must be deterministic
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Architecture](./architecture.md)
|
||||
- [CLAUDE.md](../../../CLAUDE.md) - Global coding rules
|
||||
- [src/__Tests/AGENTS.md](../../../src/__Tests/AGENTS.md) - Test infrastructure
|
||||
62
docs/modules/_template/README.md
Normal file
62
docs/modules/_template/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# <Module Name>
|
||||
|
||||
> One-line description of what this module does.
|
||||
|
||||
## Purpose
|
||||
|
||||
A brief paragraph (2-3 sentences) explaining the module's purpose, its primary responsibility, and why it exists in the StellaOps platform.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Architecture](./architecture.md) - Technical design and implementation details
|
||||
- [Operations](./operations/) - Operational runbooks and dashboards
|
||||
- [API Reference](./api/) - API documentation (if applicable)
|
||||
|
||||
## Status
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| **Maturity** | Production / Beta / Alpha |
|
||||
| **Last Reviewed** | YYYY-MM-DD |
|
||||
| **Maintainer** | Guild/Team Name |
|
||||
|
||||
## Key Features
|
||||
|
||||
- Feature 1: Brief description
|
||||
- Feature 2: Brief description
|
||||
- Feature 3: Brief description
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Upstream (this module depends on)
|
||||
- **Authority** - Authentication and authorization
|
||||
- **Other Module** - Why this dependency exists
|
||||
|
||||
### Downstream (modules that depend on this)
|
||||
- **Other Module** - How they use this module
|
||||
|
||||
## Quick Start
|
||||
|
||||
```csharp
|
||||
// Minimal code example showing how to use this module
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
builder.Services.Add<ModuleName>Services();
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```yaml
|
||||
# Minimal configuration example
|
||||
module_name:
|
||||
setting_1: value
|
||||
setting_2: value
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Implementation Guides](../../<category>/) - If applicable
|
||||
- [Related Module](../other-module/) - Cross-references
|
||||
|
||||
## Notes
|
||||
|
||||
Any important caveats, migration notes, or known issues.
|
||||
98
docs/modules/_template/architecture.md
Normal file
98
docs/modules/_template/architecture.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# <Module Name> Architecture
|
||||
|
||||
> Technical architecture specification for <Module Name>.
|
||||
|
||||
## Overview
|
||||
|
||||
High-level description of the module's architecture and how it fits into the StellaOps platform.
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Principle 1** - Description of how this module follows or implements this principle
|
||||
2. **Principle 2** - Description
|
||||
3. **Principle 3** - Description
|
||||
|
||||
## Components
|
||||
|
||||
```
|
||||
<module-name>/
|
||||
├── __Libraries/
|
||||
│ ├── StellaOps.<ModuleName>.Core/ # Core business logic
|
||||
│ ├── StellaOps.<ModuleName>.Storage/ # Data persistence (if applicable)
|
||||
│ └── StellaOps.<ModuleName>.Client/ # Client SDK (if applicable)
|
||||
├── StellaOps.<ModuleName>.WebService/ # HTTP API (if applicable)
|
||||
├── StellaOps.<ModuleName>.Worker/ # Background processing (if applicable)
|
||||
└── __Tests/
|
||||
└── StellaOps.<ModuleName>.*.Tests/ # Test projects
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
[Input Source] → [Processing] → [Output/Storage]
|
||||
```
|
||||
|
||||
Describe the primary data flow through this module.
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
### Interface 1
|
||||
```csharp
|
||||
public interface I<Something>
|
||||
{
|
||||
Task<Result> DoSomethingAsync(Input input, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
Purpose and usage of this interface.
|
||||
|
||||
### Interface 2
|
||||
```csharp
|
||||
public interface I<SomethingElse>
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Purpose and usage.
|
||||
|
||||
## Database Schema (if applicable)
|
||||
|
||||
| Table | Purpose |
|
||||
|-------|---------|
|
||||
| `schema.table_1` | Description |
|
||||
| `schema.table_2` | Description |
|
||||
|
||||
## Invariants
|
||||
|
||||
Non-negotiable design constraints that must always be maintained:
|
||||
|
||||
1. **Invariant 1** - Description and why it matters
|
||||
2. **Invariant 2** - Description
|
||||
3. **Invariant 3** - Description
|
||||
|
||||
## Error Handling
|
||||
|
||||
How errors are handled, propagated, and logged in this module.
|
||||
|
||||
## Observability
|
||||
|
||||
- **Metrics**: Key metrics exposed by this module
|
||||
- **Traces**: OpenTelemetry trace spans
|
||||
- **Logs**: Structured logging patterns
|
||||
|
||||
## Security Considerations
|
||||
|
||||
Security-relevant aspects of this module's design.
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
- Expected throughput
|
||||
- Latency budgets
|
||||
- Resource constraints
|
||||
|
||||
## References
|
||||
|
||||
- [Module README](./README.md)
|
||||
- [API Documentation](./api/)
|
||||
- [Operations Guide](./operations/)
|
||||
@@ -17,7 +17,7 @@ Platform module describes cross-cutting architecture, contracts, and guardrails
|
||||
## Key components
|
||||
- Architecture overview in `architecture-overview.md`.
|
||||
- Platform architecture summary in `architecture.md`.
|
||||
- High-level reference: `../../07_HIGH_LEVEL_ARCHITECTURE.md`.
|
||||
- High-level reference: `../../ARCHITECTURE_REFERENCE.md`.
|
||||
|
||||
## Integrations & dependencies
|
||||
- All StellaOps services via shared contracts (AOC, telemetry, security).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Ownership:** Architecture Guild • Docs Guild
|
||||
> **Audience:** Service owners, platform engineers, solution architects
|
||||
> **Related:** [High-Level Architecture](../../07_HIGH_LEVEL_ARCHITECTURE.md), [Concelier Architecture](../concelier/architecture.md), [Policy Engine Architecture](../policy/architecture.md), [Aggregation-Only Contract](../../aoc/aggregation-only-contract.md)
|
||||
> **Related:** [High-Level Architecture](../../ARCHITECTURE_REFERENCE.md), [Concelier Architecture](../concelier/architecture.md), [Policy Engine Architecture](../policy/architecture.md), [Aggregation-Only Contract](../../aoc/aggregation-only-contract.md)
|
||||
|
||||
This dossier summarises the end-to-end runtime topology after the Aggregation-Only Contract (AOC) rollout. It highlights where raw facts live, how ingest services enforce guardrails, and how downstream components consume those facts to derive policy decisions and user-facing experiences.
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
This module aggregates cross-cutting contracts and guardrails that every StellaOps service must follow.
|
||||
|
||||
## Anchors
|
||||
- High-level system view: `../../07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- High-level system view: `../../ARCHITECTURE_REFERENCE.md`
|
||||
- Architecture overview: `../../ARCHITECTURE_OVERVIEW.md`
|
||||
- Platform overview: `architecture-overview.md`
|
||||
- Platform service definition: `platform-service.md`
|
||||
- Aggregation-Only Contract: `../../aoc/aggregation-only-contract.md` (referenced across ingestion/observability docs)
|
||||
|
||||
@@ -80,16 +80,26 @@ StellaOps.Router.slnx
|
||||
|
||||
## Key Documents
|
||||
|
||||
### Module Documentation (this directory)
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [architecture.md](architecture.md) | Canonical specification and requirements |
|
||||
| [schema-validation.md](schema-validation.md) | JSON Schema validation feature |
|
||||
| [openapi-aggregation.md](openapi-aggregation.md) | OpenAPI document generation |
|
||||
| [migration-guide.md](migration-guide.md) | WebService to Microservice migration |
|
||||
| [rate-limiting.md](rate-limiting.md) | Centralized router rate limiting |
|
||||
| [rate-limiting.md](rate-limiting.md) | Centralized router rate limiting (dossier) |
|
||||
| [aspnet-endpoint-bridge.md](aspnet-endpoint-bridge.md) | Using ASP.NET endpoint registration as Router endpoint registration |
|
||||
| [messaging-valkey-transport.md](messaging-valkey-transport.md) | Messaging transport over Valkey |
|
||||
|
||||
### Implementation Guides (docs/router/)
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [README.md](../../router/README.md) | Quick start and feature overview |
|
||||
| [ARCHITECTURE.md](../../router/ARCHITECTURE.md) | Detailed architecture walkthrough |
|
||||
| [GETTING_STARTED.md](../../router/GETTING_STARTED.md) | Step-by-step setup guide |
|
||||
| [rate-limiting.md](../../router/rate-limiting.md) | Rate limiting configuration guide |
|
||||
| [transports/](../../router/transports/) | Transport plugin documentation |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Gateway
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## Evidence & scoring
|
||||
- Large confidence boost when both host (`dotnet`) and DLL artefact are present.
|
||||
- Add evidence for runtimeconfig parsing (`"runtimeconfig TFM=net8.0"`), bundle markers, or ASP.NET env vars.
|
||||
- Add evidence for runtimeconfig parsing (`"runtimeconfig TFM=net10.0"`), bundle markers, or ASP.NET env vars.
|
||||
- Penalise detections lacking artefact confirmation.
|
||||
|
||||
## Edge cases
|
||||
|
||||
@@ -277,12 +277,16 @@ dotnet run --project src/Router/examples/Examples.OrderService
|
||||
|
||||
## Documentation
|
||||
|
||||
### Implementation Guides (this directory)
|
||||
- [Architecture Overview](./ARCHITECTURE.md)
|
||||
- [Getting Started Guide](./GETTING_STARTED.md)
|
||||
- [Transport Configuration](./transports/)
|
||||
- [Rate Limiting](./rate-limiting.md)
|
||||
- [Examples](./examples/README.md)
|
||||
|
||||
### Module Dossier
|
||||
For architectural decisions, invariants, and module context, see the [Router Module Dossier](../modules/router/README.md).
|
||||
|
||||
## Related Modules
|
||||
|
||||
- **Authority**: OAuth/OIDC integration for gateway authentication
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Target framework monikers (e.g., net6.0, net8.0, netstandard2.1)"
|
||||
"description": "Target framework monikers (e.g., net8.0, net10.0, netstandard2.1)"
|
||||
},
|
||||
"assembly_analysis": {
|
||||
"$ref": "#/definitions/AssemblyAnalysisConfig"
|
||||
@@ -1483,7 +1483,7 @@
|
||||
{
|
||||
"config_id": "aspnet-core-analyzer",
|
||||
"version": "1.0.0",
|
||||
"target_frameworks": ["net6.0", "net7.0", "net8.0"],
|
||||
"target_frameworks": ["net8.0", "net9.0", "net10.0"],
|
||||
"assembly_analysis": {
|
||||
"enabled": true,
|
||||
"include_referenced_assemblies": true,
|
||||
|
||||
@@ -53,4 +53,4 @@ All SDKs and plugins must maintain determinism:
|
||||
|
||||
- [Plugin Architecture](../plugins/ARCHITECTURE.md)
|
||||
- [Plugin Configuration](../plugins/CONFIGURATION.md)
|
||||
- [Plugin SDK Guide](../10_PLUGIN_SDK_GUIDE.md)
|
||||
- [Plugin SDK Guide](../PLUGIN_SDK_GUIDE.md)
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
Use this index to locate platform-level architecture references and per-module dossiers.
|
||||
|
||||
## Core views
|
||||
- [Architecture overview (10-minute tour)](../../40_ARCHITECTURE_OVERVIEW.md)
|
||||
- [High-level architecture (reference map)](../../07_HIGH_LEVEL_ARCHITECTURE.md)
|
||||
- [Architecture overview (10-minute tour)](../../ARCHITECTURE_OVERVIEW.md)
|
||||
- [High-level architecture (reference map)](../../ARCHITECTURE_REFERENCE.md)
|
||||
- [Scanner core contracts](../../scanner-core-contracts.md)
|
||||
- [Authority (legacy overview)](../../11_AUTHORITY.md)
|
||||
- [Console operator guide](../../15_UI_GUIDE.md) and deep dives under [console](../../console/) and [ux](../../ux/)
|
||||
- [Authority (legacy overview)](../../AUTHORITY.md)
|
||||
- [Console operator guide](../../UI_GUIDE.md) and deep dives under [console](../../console/) and [ux](../../ux/)
|
||||
- [Component map](component-map.md) (quick descriptions of every module under `src/`)
|
||||
|
||||
## Detailed references
|
||||
|
||||
@@ -5,7 +5,7 @@ Concise descriptions of every top-level component under `src/`, summarising the
|
||||
## Advisory & Evidence Services
|
||||
- **AdvisoryAI** — Experimental intelligence helpers that summarise and prioritise advisory data for humans. Ingests canonical observations from Concelier/Excititor, adds explainable insights, and feeds UI/CLI and Policy workflows. See `docs/modules/advisory-ai/architecture.md`.
|
||||
- **Concelier** — Canonical advisory ingestion engine enforcing the Aggregation-Only Contract (AOC). Produces immutable observations/linksets consumed by Policy Engine, Graph, Scheduler, and Export Center. Docs in `docs/modules/concelier/architecture.md` and `docs/aoc/aggregation-only-contract.md`.
|
||||
- **Excititor** — VEX statement normaliser applying AOC guardrails. Supplies VEX observations to Policy Engine, VEX Lens, Scheduler, and UI. Reference `docs/modules/excititor/architecture.md` and `docs/16_VEX_CONSENSUS_GUIDE.md`.
|
||||
- **Excititor** — VEX statement normaliser applying AOC guardrails. Supplies VEX observations to Policy Engine, VEX Lens, Scheduler, and UI. Reference `docs/modules/excititor/architecture.md` and `docs/VEX_CONSENSUS_GUIDE.md`.
|
||||
- **VexLens** — Provides focused exploration of VEX evidence, conflict analysis, and waiver insights for UI/CLI. Backed by Excititor and Policy Engine (`docs/modules/vex-lens/architecture.md`).
|
||||
- **EvidenceLocker** — Long-term store for signed evidence bundles (DSSE, SRM, policy waivers). Integrates with Attestor, Export Center, Policy, and replay tooling (`docs/forensics/evidence-locker.md`).
|
||||
- **ExportCenter** — Packages reproducible evidence bundles and mirror artefacts for online/offline distribution. Pulls from Concelier, Excititor, Policy, Scanner, Attestor, and Registry (`docs/modules/export-center/architecture.md`).
|
||||
@@ -26,7 +26,7 @@ Concise descriptions of every top-level component under `src/`, summarising the
|
||||
- **Governance components** (Authority scopes, Policy governance, Console policy UI) are covered in `docs/security/policy-governance.md` and `docs/modules/ui/policies.md`.
|
||||
|
||||
## Identity, Signing & Provenance
|
||||
- **Authority** — Identity provider issuing short-lived OpToks, enforcing scopes/tenancy, and powering every module’s authentication story (`docs/11_AUTHORITY.md`, `docs/modules/authority/architecture.md`).
|
||||
- **Authority** — Identity provider issuing short-lived OpToks, enforcing scopes/tenancy, and powering every module's authentication story (`docs/AUTHORITY.md`, `docs/modules/authority/architecture.md`).
|
||||
- **Signer** — DSSE signing backend supporting keyless/keyful modes with Authority-managed trust roots (`docs/modules/signer/architecture.md`).
|
||||
- **Attestor** — Manages proof bundles, optional Rekor mirror, and distribution to consumers (`docs/modules/attestor/architecture.md`).
|
||||
- **Provenance** — Utilities and services for DSSE/SLSA provenance verification, consumed by Export Center, EvidenceLocker, and Replay (`docs/modules/export-center/provenance-and-signing.md`).
|
||||
@@ -49,10 +49,10 @@ Concise descriptions of every top-level component under `src/`, summarising the
|
||||
- **Registry** — Anonymous registry/token service hosting platform images and Offline Kit artefacts (`docs/modules/registry/architecture.md`).
|
||||
- **Zastava** — Runtime observer/admission controller ensuring signed images, SBOM availability, and policy verdict enforcement in live clusters (`docs/modules/zastava/architecture.md`).
|
||||
- **Signals** (shared above) plus runtime components integrate tightly with Zastava and Policy Engine.
|
||||
- **Bench** — Performance benchmarking toolset validating platform SLAs (`docs/12_PERFORMANCE_WORKBOOK.md`).
|
||||
- **Bench** — Performance benchmarking toolset validating platform SLAs (`docs/PERFORMANCE_WORKBOOK.md`).
|
||||
|
||||
## Offline, Telemetry & Infrastructure
|
||||
- **AirGap** — Bundles Offline Update Kits, enforces sealed-mode operations, and distributes trust roots/feeds (`docs/24_OFFLINE_KIT.md`, `docs/airgap/`).
|
||||
- **AirGap** — Bundles Offline Update Kits, enforces sealed-mode operations, and distributes trust roots/feeds (`docs/OFFLINE_KIT.md`, `docs/airgap/`).
|
||||
- **Telemetry** — OpenTelemetry collector/storage deployment tooling, observability integrations, and offline metrics packages (`docs/modules/telemetry/architecture.md`, `docs/observability/`).
|
||||
- **Mirror** and **ExportCenter** (above) complement AirGap by keeping offline mirrors in sync.
|
||||
- **Tools** — Collection of utility programs (fixture generators, smoke tests, migration scripts) supporting all modules (`docs/dev/fixtures.md`, module-specific tooling sections).
|
||||
@@ -67,7 +67,7 @@ Concise descriptions of every top-level component under `src/`, summarising the
|
||||
- **Aoc** library (mentioned above) is reused by ingestion components and verification tooling to enforce the Aggregation-Only Contract.
|
||||
|
||||
## How It All Connects
|
||||
High-level flows (see `docs/40_ARCHITECTURE_OVERVIEW.md` for the 10-minute tour):
|
||||
High-level flows (see `docs/ARCHITECTURE_OVERVIEW.md` for the 10-minute tour):
|
||||
1. **Ingest** — Concelier and Excititor use AOC to ingest advisories/VEX; Scheduler observes deltas.
|
||||
2. **Scan & Evaluate** — Scanner generates SBOM evidence and hands to Signer/Attestor; Policy Engine merges SBOM, advisory, VEX, runtime signals; RiskEngine prioritises.
|
||||
3. **Store & Export** — EvidenceLocker and Export Center package results; Registry serves artefacts; AirGap bundles offline editions.
|
||||
|
||||
@@ -88,11 +88,11 @@ This document provides a complete inventory of all analyzers used in StellaOps S
|
||||
│ 2. Parse legacy packages.config XML │
|
||||
│ 3. Parse *.deps.json for runtime dependencies │
|
||||
│ 4. Resolve transitive dependencies from asset files │
|
||||
│ 5. Extract framework targeting (net6.0, net8.0, etc.) │
|
||||
│ 5. Extract framework targeting (net8.0, net10.0, etc.) │
|
||||
│ │
|
||||
│ PURL Format: │
|
||||
│ pkg:nuget/Newtonsoft.Json@13.0.1 │
|
||||
│ pkg:nuget/Microsoft.Extensions.Logging@8.0.0?framework=net8.0 │
|
||||
│ pkg:nuget/Microsoft.Extensions.Logging@10.0.0?framework=net10.0 │
|
||||
│ │
|
||||
│ Special Handling: │
|
||||
│ • Framework-specific dependencies │
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
Resources for contributors building features, plug-ins, connectors, and tests.
|
||||
|
||||
## Engineering Standards & Quality
|
||||
- [../18_CODING_STANDARDS.md](../../18_CODING_STANDARDS.md) – language guidelines, project layout, review expectations.
|
||||
- [../19_TEST_SUITE_OVERVIEW.md](../../19_TEST_SUITE_OVERVIEW.md) – unit, integration, golden, and determinism test strategy.
|
||||
- [../12_PERFORMANCE_WORKBOOK.md](../../12_PERFORMANCE_WORKBOOK.md) – benchmark targets and reference rigs.
|
||||
- [../CODING_STANDARDS.md](../../CODING_STANDARDS.md) – language guidelines, project layout, review expectations.
|
||||
- [../TEST_SUITE_OVERVIEW.md](../../TEST_SUITE_OVERVIEW.md) – unit, integration, golden, and determinism test strategy.
|
||||
- [../PERFORMANCE_WORKBOOK.md](../../PERFORMANCE_WORKBOOK.md) – benchmark targets and reference rigs.
|
||||
- [../cli-vs-ui-parity.md](../../cli-vs-ui-parity.md) – CLI vs Console feature parity tracking.
|
||||
- [../scanner-core-contracts.md](../../scanner-core-contracts.md) – DTO fixtures consumed by tests.
|
||||
|
||||
## Plug-ins, Connectors & Extensions
|
||||
- [../10_PLUGIN_SDK_GUIDE.md](../../10_PLUGIN_SDK_GUIDE.md) – plug-in lifecycle, manifests, packaging.
|
||||
- [../10_CONCELIER_CLI_QUICKSTART.md](../../10_CONCELIER_CLI_QUICKSTART.md) – local Concelier + CLI workflow for advisory ingestion.
|
||||
- [../PLUGIN_SDK_GUIDE.md](../../PLUGIN_SDK_GUIDE.md) – plug-in lifecycle, manifests, packaging.
|
||||
- [../CONCELIER_CLI_QUICKSTART.md](../../CONCELIER_CLI_QUICKSTART.md) – local Concelier + CLI workflow for advisory ingestion.
|
||||
- Developer guides under [../dev/](../../dev/):
|
||||
- Connector playbooks (`30_EXCITITOR_CONNECTOR_GUIDE.md`, `kisa_connector_notes.md`).
|
||||
- Authority and DPoP guidance (`31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, `32_AUTH_CLIENT_GUIDE.md`).
|
||||
@@ -20,7 +20,7 @@ Resources for contributors building features, plug-ins, connectors, and tests.
|
||||
- Operational templates and fixtures (`templates/`, `fixtures.md`).
|
||||
|
||||
## CLI, SDKs & Automation
|
||||
- [../09_API_CLI_REFERENCE.md](../../09_API_CLI_REFERENCE.md) – authoritative CLI commands and flags (use for scripting).
|
||||
- [../API_CLI_REFERENCE.md](../../API_CLI_REFERENCE.md) – authoritative CLI commands and flags (use for scripting).
|
||||
- [../api/sdk-openapi-program.md](../../api/sdk-openapi-program.md) – guidance for downstream SDK generation.
|
||||
- [../policy/gateway.md](../../policy/gateway.md) & [../policy/dsl.md](../../policy/dsl.md) – foundations for automating policy programs.
|
||||
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
Deployment, runtime operations, and air-gap playbooks for running Stella Ops in production.
|
||||
|
||||
## Install & Upgrade
|
||||
- [../21_INSTALL_GUIDE.md](../../21_INSTALL_GUIDE.md) – canonical install guide (Docker, air-gap considerations).
|
||||
- [../INSTALL_GUIDE.md](../../INSTALL_GUIDE.md) – canonical install guide (Docker, air-gap considerations).
|
||||
- [../operations/console-docker-install.md](../../operations/console-docker-install.md) – Docker install recipes.
|
||||
- [../deploy/containers.md](../../deploy/containers.md) – container deployment guidance for AOC environments.
|
||||
- [../deploy/console.md](../../deploy/console.md) – console deployment specifics.
|
||||
- [../13_RELEASE_ENGINEERING_PLAYBOOK.md](../../13_RELEASE_ENGINEERING_PLAYBOOK.md) – release automation, signing, reproducibility.
|
||||
- [../RELEASE_ENGINEERING_PLAYBOOK.md](../../RELEASE_ENGINEERING_PLAYBOOK.md) – release automation, signing, reproducibility.
|
||||
- [../artifacts/bom-index/README.md](../../artifacts/bom-index/README.md) – BOM index artifact layout for Offline Kit exports.
|
||||
|
||||
## Offline & Sovereign Operations
|
||||
- [../quickstart.md](../../quickstart.md) – 5-minute path to first scan (useful for smoke testing installs).
|
||||
- [../24_OFFLINE_KIT.md](../../24_OFFLINE_KIT.md) – bundle contents, import/export workflow.
|
||||
- [../OFFLINE_KIT.md](../../OFFLINE_KIT.md) – bundle contents, import/export workflow.
|
||||
- [../airgap/airgap-mode.md](../../airgap/airgap-mode.md) – configuration for sealed environments.
|
||||
- [../license-jwt-quota.md](../../license-jwt-quota.md) – offline quota token lifecycle.
|
||||
- [../10_CONCELIER_CLI_QUICKSTART.md](../../10_CONCELIER_CLI_QUICKSTART.md) – workstation ingest/export workflow (operators).
|
||||
- [../CONCELIER_CLI_QUICKSTART.md](../../CONCELIER_CLI_QUICKSTART.md) – workstation ingest/export workflow (operators).
|
||||
|
||||
## Hardening & Governance
|
||||
- [../17_SECURITY_HARDENING_GUIDE.md](../../17_SECURITY_HARDENING_GUIDE.md) – platform hardening checklist.
|
||||
- [../SECURITY_HARDENING_GUIDE.md](../../SECURITY_HARDENING_GUIDE.md) – platform hardening checklist.
|
||||
- [../accessibility.md](../../accessibility.md) – accessibility checklist for console deployments.
|
||||
- [../security/console-security.md](../../security/console-security.md) – console-specific controls.
|
||||
- [../security/authority-scopes.md](../../security/authority-scopes.md) – Authority scope model.
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
Authoritative sources for threat models, governance, compliance, and security operations.
|
||||
|
||||
## Policies & Governance
|
||||
- [../13_SECURITY_POLICY.md](../../13_SECURITY_POLICY.md) – responsible disclosure, support windows.
|
||||
- [../11_GOVERNANCE.md](../../11_GOVERNANCE.md) – project governance charter.
|
||||
- [../12_CODE_OF_CONDUCT.md](../../12_CODE_OF_CONDUCT.md) – community expectations.
|
||||
- [../17_SECURITY_HARDENING_GUIDE.md](../../17_SECURITY_HARDENING_GUIDE.md) – deployment hardening steps.
|
||||
- [../SECURITY_POLICY.md](../../SECURITY_POLICY.md) – responsible disclosure, support windows.
|
||||
- [../GOVERNANCE.md](../../GOVERNANCE.md) – project governance charter.
|
||||
- [../CODE_OF_CONDUCT.md](../../CODE_OF_CONDUCT.md) – community expectations.
|
||||
- [../SECURITY_HARDENING_GUIDE.md](../../SECURITY_HARDENING_GUIDE.md) – deployment hardening steps.
|
||||
- [../security/policy-governance.md](../../security/policy-governance.md) – policy governance specifics.
|
||||
- [../29_LEGAL_FAQ_QUOTA.md](../../29_LEGAL_FAQ_QUOTA.md) – legal interpretation of quota.
|
||||
- [../33_333_QUOTA_OVERVIEW.md](../../33_333_QUOTA_OVERVIEW.md) – quota policy reference.
|
||||
- [../LEGAL_FAQ_QUOTA.md](../../LEGAL_FAQ_QUOTA.md) – legal interpretation of quota.
|
||||
- [../QUOTA_OVERVIEW.md](../../QUOTA_OVERVIEW.md) – quota policy reference.
|
||||
- [../risk/risk-profiles.md](../../risk/risk-profiles.md) – organisational risk personas.
|
||||
|
||||
## Threat Models & Security Architecture
|
||||
@@ -25,8 +25,8 @@ Authoritative sources for threat models, governance, compliance, and security op
|
||||
- [../security/audit-events.md](../../security/audit-events.md) – audit event taxonomy.
|
||||
- [../security/revocation-bundle.md](../../security/revocation-bundle.md) & [../security/revocation-bundle-example.json](../../security/revocation-bundle-example.json) – revocation process.
|
||||
- [../license-jwt-quota.md](../../license-jwt-quota.md) – licence/quota enforcement controls.
|
||||
- [../30_QUOTA_ENFORCEMENT_FLOW1.md](../../30_QUOTA_ENFORCEMENT_FLOW1.md) – quota enforcement sequence.
|
||||
- [../24_OFFLINE_KIT.md](../../24_OFFLINE_KIT.md) – tamper-evident offline artefacts.
|
||||
- [../QUOTA_ENFORCEMENT_FLOW.md](../../QUOTA_ENFORCEMENT_FLOW.md) – quota enforcement sequence.
|
||||
- [../OFFLINE_KIT.md](../../OFFLINE_KIT.md) – tamper-evident offline artefacts.
|
||||
- [../security/](../../security/) – browse for additional deep dives (audit, scopes, rate limits).
|
||||
|
||||
## Supporting Material
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
Foundational, high-level documents that define StellaOps direction, scope, and differentiators.
|
||||
|
||||
- [Vision](../../03_VISION.md) — north-star goals, KPIs, and themes.
|
||||
- [Feature matrix](../../04_FEATURE_MATRIX.md) — capability matrix by tier.
|
||||
- [System requirements spec](../../05_SYSTEM_REQUIREMENTS_SPEC.md) — functional and non-functional requirements baseline.
|
||||
- [Roadmap](../../05_ROADMAP.md) — date-free capability roadmap and definition of “done”.
|
||||
- [Architecture overview](../../40_ARCHITECTURE_OVERVIEW.md) — platform principles and module map.
|
||||
- [Vision](../../VISION.md) — north-star goals, KPIs, and themes.
|
||||
- [Feature matrix](../../FEATURE_MATRIX.md) — capability matrix by tier.
|
||||
- [System requirements spec](../../SYSTEM_REQUIREMENTS_SPEC.md) — functional and non-functional requirements baseline.
|
||||
- [Roadmap](../../ROADMAP.md) — date-free capability roadmap and definition of "done".
|
||||
- [Architecture overview](../../ARCHITECTURE_OVERVIEW.md) — platform principles and module map.
|
||||
- [Moat](../../moat.md) — differentiating workstreams (determinism, policy lattice, sovereign crypto readiness, attestation graph).
|
||||
- [Offline Kit](../../24_OFFLINE_KIT.md) — offline story and workflows.
|
||||
- [Security policy](../../13_SECURITY_POLICY.md) — disclosure and support expectations.
|
||||
- [Glossary](../../14_GLOSSARY_OF_TERMS.md) — canonical vocabulary.
|
||||
- [UI guide](../../15_UI_GUIDE.md) — console UX overview for evaluators.
|
||||
- [FAQ matrix](../../23_FAQ_MATRIX.md) — stakeholder FAQ.
|
||||
- [Offline Kit](../../OFFLINE_KIT.md) — offline story and workflows.
|
||||
- [Security policy](../../SECURITY_POLICY.md) — disclosure and support expectations.
|
||||
- [Glossary](../../GLOSSARY.md) — canonical vocabulary.
|
||||
- [UI guide](../../UI_GUIDE.md) — console UX overview for evaluators.
|
||||
- [FAQ matrix](../../FAQ_MATRIX.md) — stakeholder FAQ.
|
||||
|
||||
## Related concepts
|
||||
- [Quota framing](../../33_333_QUOTA_OVERVIEW.md) and [enforcement flow](../../30_QUOTA_ENFORCEMENT_FLOW1.md) align business policy with enforcement diagrams.
|
||||
- [Legal FAQ (quota)](../../29_LEGAL_FAQ_QUOTA.md) captures the AGPL-3.0 interpretation of quota enforcement.
|
||||
- [Quota framing](../../QUOTA_OVERVIEW.md) and [enforcement flow](../../QUOTA_ENFORCEMENT_FLOW.md) align business policy with enforcement diagrams.
|
||||
- [Legal FAQ (quota)](../../LEGAL_FAQ_QUOTA.md) captures the AGPL-3.0 interpretation of quota enforcement.
|
||||
- [License/JWT quota narrative](../../license-jwt-quota.md) documents the offline licensing story for quota tokens.
|
||||
|
||||
@@ -161,7 +161,9 @@ npm run storybook:build
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Test Suite Overview](../TEST_SUITE_OVERVIEW.md) - High-level entry point for testing
|
||||
- [CI/CD Overview](../cicd/README.md)
|
||||
- [CI/CD Test Strategy](../cicd/test-strategy.md) - Detailed CI/CD test integration
|
||||
- [Workflow Triggers](../cicd/workflow-triggers.md)
|
||||
- [Path Filters](../cicd/path-filters.md)
|
||||
- [Test Infrastructure](../../src/__Tests/AGENTS.md)
|
||||
|
||||
@@ -149,9 +149,9 @@ If baselines become stale:
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Test Suite Overview](../19_TEST_SUITE_OVERVIEW.md)
|
||||
- [Test Suite Overview](../TEST_SUITE_OVERVIEW.md)
|
||||
- [Testing Strategy Models](./testing-strategy-models.md)
|
||||
- [Test Catalog](./TEST_CATALOG.yml)
|
||||
- [Reachability Corpus Plan](../reachability/corpus-plan.md)
|
||||
- [Performance Workbook](../12_PERFORMANCE_WORKBOOK.md)
|
||||
- [Performance Workbook](../PERFORMANCE_WORKBOOK.md)
|
||||
- [Testing Quality Guardrails](./testing-quality-guardrails-implementation.md)
|
||||
|
||||
@@ -41,12 +41,12 @@ Supersedes/extends: `docs/product-advisories/archived/2025-12-21-testing-strateg
|
||||
|
||||
## Documentation moments (when to update)
|
||||
- New model or required test type: update `docs/testing/TEST_CATALOG.yml`.
|
||||
- New lane or gate: update `docs/19_TEST_SUITE_OVERVIEW.md` and `docs/testing/ci-quality-gates.md`.
|
||||
- New lane or gate: update `docs/TEST_SUITE_OVERVIEW.md` and `docs/testing/ci-quality-gates.md`.
|
||||
- Module-specific test policy change: update the module dossier under `docs/modules/<module>/`.
|
||||
- New fixtures or runnable harnesses: place under `docs/benchmarks/**` or `tests/**` and link here.
|
||||
|
||||
## Related artifacts
|
||||
- Test catalog (source of truth): `docs/testing/TEST_CATALOG.yml`
|
||||
- Test suite overview: `docs/19_TEST_SUITE_OVERVIEW.md`
|
||||
- Test suite overview: `docs/TEST_SUITE_OVERVIEW.md`
|
||||
- Quality guardrails: `docs/testing/testing-quality-guardrails-implementation.md`
|
||||
- Code samples from the advisory: `docs/benchmarks/testing/better-testing-strategy-samples.md`
|
||||
|
||||
@@ -72,6 +72,11 @@ public sealed class HeaderScopeAuthenticationHandler : AuthenticationHandler<Aut
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var scope in value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||
{
|
||||
scopes.Add(scope);
|
||||
|
||||
@@ -72,7 +72,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
null,
|
||||
new[] { new FeedBuildConfig("nvd-feed", "nvd", "2025-06-15", feedPath, "feeds/nvd.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundleOutputPath = Path.Combine(_onlineEnvPath, "bundle");
|
||||
|
||||
@@ -120,7 +121,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
DateTimeOffset.UtcNow.AddDays(30),
|
||||
new[] { new FeedBuildConfig("feed-1", "nvd", "v1", feedPath, "feeds/all-feeds.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative) },
|
||||
new[] { new PolicyBuildConfig("policy-1", "default", "1.0", policyPath, "policies/default.rego", PolicyType.OpaRego) },
|
||||
new[] { new CryptoBuildConfig("crypto-1", "trust-root", certPath, "certs/root.pem", CryptoComponentType.TrustRoot, null) });
|
||||
new[] { new CryptoBuildConfig("crypto-1", "trust-root", certPath, "certs/root.pem", CryptoComponentType.TrustRoot, null) },
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath = Path.Combine(_onlineEnvPath, "multi-bundle");
|
||||
|
||||
@@ -161,7 +163,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
null,
|
||||
new[] { new FeedBuildConfig("feed", "nvd", "v1", feedPath, "feeds/nvd.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath = Path.Combine(_onlineEnvPath, "corrupt-source");
|
||||
var manifest = await builder.BuildAsync(request, bundlePath);
|
||||
@@ -219,7 +222,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
null,
|
||||
Array.Empty<FeedBuildConfig>(),
|
||||
new[] { new PolicyBuildConfig("security-policy", "security", "1.0", policyPath, "policies/security.rego", PolicyType.OpaRego) },
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath = Path.Combine(_onlineEnvPath, "policy-bundle");
|
||||
|
||||
@@ -273,7 +277,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
new PolicyBuildConfig("policy-2", "policy2", "1.0", policy2Path, "policies/policy2.rego", PolicyType.OpaRego),
|
||||
new PolicyBuildConfig("policy-3", "policy3", "1.0", policy3Path, "policies/policy3.rego", PolicyType.OpaRego)
|
||||
},
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath = Path.Combine(_onlineEnvPath, "multi-policy");
|
||||
|
||||
@@ -315,7 +320,8 @@ public sealed class AirGapIntegrationTests : IDisposable
|
||||
null,
|
||||
Array.Empty<FeedBuildConfig>(),
|
||||
new[] { new PolicyBuildConfig("signed-policy", "signed", "1.0", policyPath, "policies/signed.rego", PolicyType.OpaRego) },
|
||||
new[] { new CryptoBuildConfig("signing-cert", "signing", certPath, "certs/signing.pem", CryptoComponentType.SigningKey, null) });
|
||||
new[] { new CryptoBuildConfig("signing-cert", "signing", certPath, "certs/signing.pem", CryptoComponentType.SigningKey, null) },
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath = Path.Combine(_onlineEnvPath, "signed-bundle");
|
||||
|
||||
|
||||
@@ -142,7 +142,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act - First export
|
||||
var manifest1 = await builder.BuildAsync(request, outputPath1);
|
||||
@@ -163,7 +164,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var manifest2 = await builder.BuildAsync(request2, outputPath2);
|
||||
|
||||
@@ -278,7 +280,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f3", "osv", "v1", feed3, "feeds/f3.json", DateTimeOffset.UtcNow, FeedFormat.OsvJson)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, Path.Combine(_tempRoot, "multi"));
|
||||
@@ -332,7 +335,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f1", "binary", "v1", source1, "data/binary.bin", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var request2 = new BundleBuildRequest(
|
||||
"binary-test",
|
||||
@@ -343,7 +347,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f1", "binary", "v1", source2, "data/binary.bin", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest1 = await builder.BuildAsync(request1, Path.Combine(_tempRoot, "bin1"));
|
||||
@@ -407,7 +412,8 @@ public sealed class BundleDeterminismTests : IAsyncLifetime
|
||||
new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero), FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
}
|
||||
|
||||
private BundleManifest CreateDeterministicManifest(string name)
|
||||
|
||||
@@ -259,7 +259,8 @@ public sealed class BundleExportImportTests : IDisposable
|
||||
null,
|
||||
new[] { new FeedBuildConfig("feed-1", "nvd", "v1", feedFile1, "feeds/nvd.json", DateTimeOffset.Parse("2025-01-01T00:00:00Z"), FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var request2 = new BundleBuildRequest(
|
||||
"determinism-test",
|
||||
@@ -267,7 +268,8 @@ public sealed class BundleExportImportTests : IDisposable
|
||||
null,
|
||||
new[] { new FeedBuildConfig("feed-1", "nvd", "v1", feedFile2, "feeds/nvd.json", DateTimeOffset.Parse("2025-01-01T00:00:00Z"), FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var outputPath1 = Path.Combine(_tempRoot, "determinism-output1");
|
||||
var outputPath2 = Path.Combine(_tempRoot, "determinism-output2");
|
||||
@@ -363,7 +365,8 @@ public sealed class BundleExportImportTests : IDisposable
|
||||
imported.Feeds[0].SnapshotAt,
|
||||
imported.Feeds[0].Format) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var bundlePath2 = Path.Combine(_tempRoot, "roundtrip2");
|
||||
var manifest2 = await builder.BuildAsync(reexportRequest, bundlePath2);
|
||||
@@ -409,7 +412,8 @@ public sealed class BundleExportImportTests : IDisposable
|
||||
null,
|
||||
new[] { new FeedBuildConfig("feed-1", "nvd", "v1", feedSourcePath, "feeds/nvd.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
}
|
||||
|
||||
private static BundleManifest CreateTestManifest()
|
||||
|
||||
@@ -49,7 +49,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
null,
|
||||
Array.Empty<FeedBuildConfig>(),
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -93,7 +94,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -139,7 +141,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
"policies/default.rego",
|
||||
PolicyType.OpaRego)
|
||||
},
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -182,7 +185,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
"certs/root.pem",
|
||||
CryptoComponentType.TrustRoot,
|
||||
DateTimeOffset.UtcNow.AddYears(10))
|
||||
});
|
||||
},
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -225,7 +229,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
{
|
||||
new PolicyBuildConfig("p1", "default", "1.0", policy, "policies/default.rego", PolicyType.OpaRego)
|
||||
},
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -261,7 +266,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f1", "test", "v1", feedFile, "feeds/test.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -288,7 +294,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f1", "test", "v1", feedFile, "feeds/test.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -328,7 +335,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new[]
|
||||
{
|
||||
new CryptoBuildConfig("c1", "root", certFile, "crypto/certs/ca/root.pem", CryptoComponentType.TrustRoot, null)
|
||||
});
|
||||
},
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -369,7 +377,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new FeedBuildConfig("f1", "test", "v1", feedFile, "feeds/test.json", DateTimeOffset.UtcNow, format)
|
||||
},
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -404,7 +413,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
{
|
||||
new PolicyBuildConfig("p1", "test", "1.0", policyFile, "policies/test", type)
|
||||
},
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -440,7 +450,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new[]
|
||||
{
|
||||
new CryptoBuildConfig("c1", "test", certFile, "certs/test", type, null)
|
||||
});
|
||||
},
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -468,7 +479,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
expiresAt,
|
||||
Array.Empty<FeedBuildConfig>(),
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
@@ -496,7 +508,8 @@ public sealed class BundleExportTests : IAsyncLifetime
|
||||
new[]
|
||||
{
|
||||
new CryptoBuildConfig("c1", "root", certFile, "certs/root.pem", CryptoComponentType.TrustRoot, componentExpiry)
|
||||
});
|
||||
},
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
// Act
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
|
||||
@@ -49,7 +49,8 @@ public class BundleManifestTests
|
||||
null,
|
||||
new[] { new FeedBuildConfig("feed-1", "nvd", "v1", sourceFile, "feeds/nvd.json", DateTimeOffset.UtcNow, FeedFormat.StellaOpsNative) },
|
||||
Array.Empty<PolicyBuildConfig>(),
|
||||
Array.Empty<CryptoBuildConfig>());
|
||||
Array.Empty<CryptoBuildConfig>(),
|
||||
Array.Empty<RuleBundleBuildConfig>());
|
||||
|
||||
var outputPath = Path.Combine(tempRoot, "bundle");
|
||||
var manifest = await builder.BuildAsync(request, outputPath);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
|
||||
<ProjectReference Include="../../../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
|
||||
<ProjectReference Include="../../../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Go/StellaOps.Scanner.Analyzers.Lang.Go.csproj" />
|
||||
<ProjectReference Include="../../../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Java/StellaOps.Scanner.Analyzers.Lang.Java.csproj" />
|
||||
<ProjectReference Include="../../../../Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Python/StellaOps.Scanner.Analyzers.Lang.Python.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -26,10 +26,12 @@ internal sealed class ForensicVerifier : IForensicVerifier
|
||||
};
|
||||
|
||||
private readonly ILogger<ForensicVerifier> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ForensicVerifier(ILogger<ForensicVerifier> logger)
|
||||
public ForensicVerifier(ILogger<ForensicVerifier> logger, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ForensicVerificationResult> VerifyBundleAsync(
|
||||
@@ -42,7 +44,7 @@ internal sealed class ForensicVerifier : IForensicVerifier
|
||||
|
||||
var errors = new List<ForensicVerificationError>();
|
||||
var warnings = new List<string>();
|
||||
var verifiedAt = DateTimeOffset.UtcNow;
|
||||
var verifiedAt = _timeProvider.GetUtcNow();
|
||||
|
||||
_logger.LogDebug("Verifying forensic bundle at {BundlePath}", bundlePath);
|
||||
|
||||
@@ -440,7 +442,7 @@ internal sealed class ForensicVerifier : IForensicVerifier
|
||||
matchingRoot.PublicKey);
|
||||
|
||||
// Check time validity
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var timeValid = (!matchingRoot.NotBefore.HasValue || now >= matchingRoot.NotBefore.Value) &&
|
||||
(!matchingRoot.NotAfter.HasValue || now <= matchingRoot.NotAfter.Value);
|
||||
|
||||
|
||||
@@ -17,17 +17,20 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
private readonly ITrustPolicyLoader _trustPolicyLoader;
|
||||
private readonly IDsseSignatureVerifier _dsseVerifier;
|
||||
private readonly ILogger<ImageAttestationVerifier> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ImageAttestationVerifier(
|
||||
IOciRegistryClient registryClient,
|
||||
ITrustPolicyLoader trustPolicyLoader,
|
||||
IDsseSignatureVerifier dsseVerifier,
|
||||
ILogger<ImageAttestationVerifier> logger)
|
||||
ILogger<ImageAttestationVerifier> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_registryClient = registryClient ?? throw new ArgumentNullException(nameof(registryClient));
|
||||
_trustPolicyLoader = trustPolicyLoader ?? throw new ArgumentNullException(nameof(trustPolicyLoader));
|
||||
_dsseVerifier = dsseVerifier ?? throw new ArgumentNullException(nameof(dsseVerifier));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ImageVerificationResult> VerifyAsync(
|
||||
@@ -51,7 +54,7 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
ImageDigest = digest,
|
||||
Registry = reference.Registry,
|
||||
Repository = reference.Repository,
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
OciReferrersResponse referrers;
|
||||
@@ -191,7 +194,7 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
Digest = candidate.Digest,
|
||||
SignerIdentity = verification.KeyId,
|
||||
Message = verification.Error ?? "Signature verification failed",
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,7 +209,7 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
Digest = candidate.Digest,
|
||||
SignerIdentity = signerKeyId,
|
||||
Message = "Signer not allowed by trust policy",
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -220,14 +223,14 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
Digest = candidate.Digest,
|
||||
SignerIdentity = signerKeyId,
|
||||
Message = "Rekor receipt missing",
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
if (policy.MaxAge.HasValue)
|
||||
{
|
||||
var created = GetCreatedAt(candidate);
|
||||
if (created.HasValue && DateTimeOffset.UtcNow - created.Value > policy.MaxAge.Value)
|
||||
if (created.HasValue && _timeProvider.GetUtcNow() - created.Value > policy.MaxAge.Value)
|
||||
{
|
||||
return new AttestationVerification
|
||||
{
|
||||
@@ -237,7 +240,7 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
Digest = candidate.Digest,
|
||||
SignerIdentity = signerKeyId,
|
||||
Message = "Attestation exceeded max age",
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -250,7 +253,7 @@ public sealed class ImageAttestationVerifier : IImageAttestationVerifier
|
||||
Digest = candidate.Digest,
|
||||
SignerIdentity = signerKeyId,
|
||||
Message = "Signature valid",
|
||||
VerifiedAt = DateTimeOffset.UtcNow
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed class CertFrFeedClient
|
||||
}
|
||||
|
||||
var advisoryId = ResolveAdvisoryId(itemElement, detailUri);
|
||||
items.Add(new CertFrFeedItem(advisoryId, detailUri, published.ToUniversalTime(), title, summary));
|
||||
items.Add(new CertFrFeedItem(advisoryId, detailUri, published.Value.ToUniversalTime(), title, summary));
|
||||
}
|
||||
|
||||
_diagnostics.FeedFetchSuccess(items.Count);
|
||||
|
||||
@@ -73,6 +73,7 @@ public sealed class CachePerformanceBenchmarkTests : IAsyncLifetime
|
||||
_cacheService = new ValkeyAdvisoryCacheService(
|
||||
_connectionFactory,
|
||||
options,
|
||||
metrics: null,
|
||||
NullLogger<ValkeyAdvisoryCacheService>.Instance);
|
||||
|
||||
await ValueTask.CompletedTask;
|
||||
|
||||
@@ -9,13 +9,16 @@ public sealed class ExportRetentionService : IExportRetentionService
|
||||
{
|
||||
private readonly IExportRetentionStore _retentionStore;
|
||||
private readonly ILogger<ExportRetentionService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ExportRetentionService(
|
||||
IExportRetentionStore retentionStore,
|
||||
ILogger<ExportRetentionService> logger)
|
||||
ILogger<ExportRetentionService> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_retentionStore = retentionStore ?? throw new ArgumentNullException(nameof(retentionStore));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -26,7 +29,7 @@ public sealed class ExportRetentionService : IExportRetentionService
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var retention = request.OverrideRetention ?? new ExportRetentionConfig();
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Starting retention prune for tenant {TenantId}, profile {ProfileId}, execute={Execute}",
|
||||
|
||||
@@ -11,6 +11,12 @@ public sealed class InMemoryExportScheduleStore : IExportScheduleStore
|
||||
private readonly ConcurrentDictionary<Guid, Guid> _runToProfile = new();
|
||||
private readonly ConcurrentDictionary<Guid, List<ScheduledProfileInfo>> _profilesByTenant = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public InMemoryExportScheduleStore(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a profile for testing.
|
||||
@@ -106,7 +112,7 @@ public sealed class InMemoryExportScheduleStore : IExportScheduleStore
|
||||
{
|
||||
if (_statusByProfile.TryGetValue(profileId, out var existing))
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
var newFailureCount = success ? 0 : existing.ConsecutiveFailures + 1;
|
||||
|
||||
_statusByProfile[profileId] = existing with
|
||||
|
||||
@@ -32,12 +32,14 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
};
|
||||
|
||||
private readonly ILogger<LineageEvidencePackService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly ConcurrentDictionary<Guid, CachedPack> _packCache = new();
|
||||
private readonly string _tempDirectory;
|
||||
|
||||
public LineageEvidencePackService(ILogger<LineageEvidencePackService> logger)
|
||||
public LineageEvidencePackService(ILogger<LineageEvidencePackService> logger, TimeProvider? timeProvider = null)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), "stellaops-evidence-packs");
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
@@ -187,7 +189,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
Entries = entries.ToImmutableArray(),
|
||||
TotalSizeBytes = entries.Sum(e => e.SizeBytes),
|
||||
FileCount = entries.Count,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
CreatedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
|
||||
// Write manifest
|
||||
@@ -205,7 +207,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
VexVerdictDigests = vexDocuments.Select(v => v.Digest).ToImmutableArray(),
|
||||
PolicyVerdictDigest = policyVerdict?.Digest,
|
||||
ReplayHash = ComputeReplayHash(artifactDigest, sbomDigest, manifest.MerkleRoot),
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
GeneratedAt = _timeProvider.GetUtcNow(),
|
||||
Attestations = attestations.ToImmutableArray(),
|
||||
SbomDocuments = sbomDocuments.ToImmutableArray(),
|
||||
VexDocuments = vexDocuments.ToImmutableArray(),
|
||||
@@ -224,7 +226,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
{
|
||||
Pack = pack,
|
||||
ZipPath = zipPath,
|
||||
ExpiresAt = DateTimeOffset.UtcNow.AddHours(24)
|
||||
ExpiresAt = _timeProvider.GetUtcNow().AddHours(24)
|
||||
};
|
||||
|
||||
// Clean up temp directory
|
||||
@@ -246,7 +248,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
Success = true,
|
||||
Pack = pack,
|
||||
DownloadUrl = $"/api/v1/lineage/export/{packId}/download",
|
||||
ExpiresAt = DateTimeOffset.UtcNow.AddHours(24),
|
||||
ExpiresAt = _timeProvider.GetUtcNow().AddHours(24),
|
||||
SizeBytes = zipInfo.Length,
|
||||
Warnings = warnings.ToImmutableArray()
|
||||
};
|
||||
@@ -268,7 +270,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
string tenantId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > _timeProvider.GetUtcNow())
|
||||
{
|
||||
if (cached.Pack.TenantId == tenantId)
|
||||
{
|
||||
@@ -285,7 +287,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
string tenantId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
if (_packCache.TryGetValue(packId, out var cached) && cached.ExpiresAt > _timeProvider.GetUtcNow())
|
||||
{
|
||||
if (cached.Pack.TenantId == tenantId && File.Exists(cached.ZipPath))
|
||||
{
|
||||
@@ -347,7 +349,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
bomFormat = "CycloneDX",
|
||||
specVersion = "1.6",
|
||||
version = 1,
|
||||
metadata = new { timestamp = DateTimeOffset.UtcNow.ToString("O") },
|
||||
metadata = new { timestamp = _timeProvider.GetUtcNow().ToString("O") },
|
||||
components = Array.Empty<object>()
|
||||
}, JsonOptions);
|
||||
|
||||
@@ -383,7 +385,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
dataLicense = "CC0-1.0",
|
||||
name = artifactDigest,
|
||||
documentNamespace = $"https://stellaops.io/spdx/{artifactDigest}",
|
||||
creationInfo = new { created = DateTimeOffset.UtcNow.ToString("O") },
|
||||
creationInfo = new { created = _timeProvider.GetUtcNow().ToString("O") },
|
||||
packages = Array.Empty<object>()
|
||||
}, JsonOptions);
|
||||
|
||||
@@ -418,7 +420,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
context = "https://openvex.dev/ns/v0.2.0",
|
||||
id = $"urn:stellaops:vex:{artifactDigest}",
|
||||
author = "StellaOps",
|
||||
timestamp = DateTimeOffset.UtcNow.ToString("O"),
|
||||
timestamp = _timeProvider.GetUtcNow().ToString("O"),
|
||||
statements = Array.Empty<object>()
|
||||
}, JsonOptions);
|
||||
|
||||
@@ -457,7 +459,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
tenantId,
|
||||
verdict = "pass",
|
||||
policyVersion = "1.0.0",
|
||||
evaluatedAt = DateTimeOffset.UtcNow.ToString("O"),
|
||||
evaluatedAt = _timeProvider.GetUtcNow().ToString("O"),
|
||||
rules = new { total = 0, passed = 0, failed = 0, warned = 0 }
|
||||
}, JsonOptions);
|
||||
|
||||
@@ -477,7 +479,7 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
RulesPassed = 0,
|
||||
RulesFailed = 0,
|
||||
RulesWarned = 0,
|
||||
EvaluatedAt = DateTimeOffset.UtcNow,
|
||||
EvaluatedAt = _timeProvider.GetUtcNow(),
|
||||
FileName = fileName
|
||||
};
|
||||
}
|
||||
@@ -528,9 +530,9 @@ public sealed class LineageEvidencePackService : ILineageEvidencePackService
|
||||
return ComputeHash(combined);
|
||||
}
|
||||
|
||||
private static string ComputeReplayHash(string artifactDigest, string sbomDigest, string merkleRoot)
|
||||
private string ComputeReplayHash(string artifactDigest, string sbomDigest, string merkleRoot)
|
||||
{
|
||||
var input = $"{artifactDigest}|{sbomDigest}|{merkleRoot}|{DateTimeOffset.UtcNow:O}";
|
||||
var input = $"{artifactDigest}|{sbomDigest}|{merkleRoot}|{_timeProvider.GetUtcNow():O}";
|
||||
return $"sha256:{ComputeHash(input)}";
|
||||
}
|
||||
|
||||
|
||||
@@ -149,14 +149,15 @@ public sealed record ExportVerificationResult
|
||||
/// <summary>
|
||||
/// When verification was performed.
|
||||
/// </summary>
|
||||
public DateTimeOffset VerifiedAt { get; init; } = DateTimeOffset.UtcNow;
|
||||
public required DateTimeOffset VerifiedAt { get; init; }
|
||||
|
||||
public static ExportVerificationResult Failed(Guid runId, params VerificationError[] errors)
|
||||
public static ExportVerificationResult Failed(Guid runId, DateTimeOffset verifiedAt, params VerificationError[] errors)
|
||||
=> new()
|
||||
{
|
||||
Status = VerificationStatus.Invalid,
|
||||
RunId = runId,
|
||||
Errors = errors
|
||||
Errors = errors,
|
||||
VerifiedAt = verifiedAt
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,22 +14,26 @@ public sealed class ExportVerificationService : IExportVerificationService
|
||||
private readonly IExportArtifactStore _artifactStore;
|
||||
private readonly IPackRunAttestationStore? _packRunStore;
|
||||
private readonly ILogger<ExportVerificationService> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public ExportVerificationService(
|
||||
IExportArtifactStore artifactStore,
|
||||
ILogger<ExportVerificationService> logger)
|
||||
: this(artifactStore, null, logger)
|
||||
ILogger<ExportVerificationService> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
: this(artifactStore, null, logger, timeProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public ExportVerificationService(
|
||||
IExportArtifactStore artifactStore,
|
||||
IPackRunAttestationStore? packRunStore,
|
||||
ILogger<ExportVerificationService> logger)
|
||||
ILogger<ExportVerificationService> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_artifactStore = artifactStore ?? throw new ArgumentNullException(nameof(artifactStore));
|
||||
_packRunStore = packRunStore;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -52,6 +56,7 @@ public sealed class ExportVerificationService : IExportVerificationService
|
||||
{
|
||||
return ExportVerificationResult.Failed(
|
||||
request.RunId,
|
||||
_timeProvider.GetUtcNow(),
|
||||
new VerificationError
|
||||
{
|
||||
Code = VerificationErrorCodes.ManifestNotFound,
|
||||
@@ -64,6 +69,7 @@ public sealed class ExportVerificationService : IExportVerificationService
|
||||
{
|
||||
return ExportVerificationResult.Failed(
|
||||
request.RunId,
|
||||
_timeProvider.GetUtcNow(),
|
||||
new VerificationError
|
||||
{
|
||||
Code = VerificationErrorCodes.TenantMismatch,
|
||||
@@ -234,7 +240,8 @@ public sealed class ExportVerificationService : IExportVerificationService
|
||||
Encryption = encryptionResult,
|
||||
Attestation = attestationStatus,
|
||||
Errors = errors,
|
||||
Warnings = warnings
|
||||
Warnings = warnings,
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
private readonly IExceptionApplicationRepository _applicationRepository;
|
||||
private readonly ConcurrentDictionary<string, ReportJob> _jobs = new();
|
||||
private readonly ILogger<ExceptionReportGenerator> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -30,11 +31,13 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
public ExceptionReportGenerator(
|
||||
IExceptionRepository exceptionRepository,
|
||||
IExceptionApplicationRepository applicationRepository,
|
||||
ILogger<ExceptionReportGenerator> logger)
|
||||
ILogger<ExceptionReportGenerator> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_exceptionRepository = exceptionRepository;
|
||||
_applicationRepository = applicationRepository;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<ExceptionReportJobResponse> CreateReportAsync(
|
||||
@@ -42,7 +45,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var jobId = $"exc-rpt-{Guid.NewGuid():N}";
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
var job = new ReportJob
|
||||
{
|
||||
@@ -151,7 +154,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
try
|
||||
{
|
||||
job.Status = "running";
|
||||
job.StartedAt = DateTimeOffset.UtcNow;
|
||||
job.StartedAt = _timeProvider.GetUtcNow();
|
||||
|
||||
var filter = job.Request.Filter ?? new ExceptionFilter
|
||||
{
|
||||
@@ -232,7 +235,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
var document = new ExceptionReportDocument
|
||||
{
|
||||
ReportId = job.JobId,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
GeneratedAt = _timeProvider.GetUtcNow(),
|
||||
TenantId = job.TenantId,
|
||||
RequesterId = job.RequesterId,
|
||||
Title = job.Request.Title ?? "Exception Report",
|
||||
@@ -289,7 +292,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
job.ContentHash = $"sha256:{Convert.ToHexString(SHA256.HashData(content)).ToLowerInvariant()}";
|
||||
job.Progress = 100;
|
||||
job.Status = "completed";
|
||||
job.CompletedAt = DateTimeOffset.UtcNow;
|
||||
job.CompletedAt = _timeProvider.GetUtcNow();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Completed exception report {JobId} with {Count} exceptions, {Size} bytes",
|
||||
@@ -300,7 +303,7 @@ public sealed class ExceptionReportGenerator : IExceptionReportGenerator
|
||||
_logger.LogError(ex, "Failed to generate exception report {JobId}", job.JobId);
|
||||
job.Status = "failed";
|
||||
job.ErrorMessage = ex.Message;
|
||||
job.CompletedAt = DateTimeOffset.UtcNow;
|
||||
job.CompletedAt = _timeProvider.GetUtcNow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@ namespace StellaOps.Findings.Ledger.WebService.Mappings;
|
||||
|
||||
public static class LedgerEventMapping
|
||||
{
|
||||
public static LedgerEventDraft ToDraft(this LedgerEventRequest request)
|
||||
public static LedgerEventDraft ToDraft(this LedgerEventRequest request, TimeProvider? timeProvider = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
var recordedAt = (request.RecordedAt ?? DateTimeOffset.UtcNow).ToUniversalTime();
|
||||
timeProvider ??= TimeProvider.System;
|
||||
var recordedAt = (request.RecordedAt ?? timeProvider.GetUtcNow()).ToUniversalTime();
|
||||
var payload = request.Payload is null ? new JsonObject() : (JsonObject)request.Payload.DeepClone();
|
||||
|
||||
var eventObject = new JsonObject
|
||||
|
||||
@@ -1768,6 +1768,7 @@ app.MapPost("/v1/alerts/{alertId}/bundle/verify", async Task<Results<Ok<BundleVe
|
||||
[FromBody] BundleVerificationRequest request,
|
||||
[FromServices] IAlertService alertService,
|
||||
[FromServices] IEvidenceBundleService bundleService,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var alert = await alertService.GetAlertAsync(alertId, cancellationToken).ConfigureAwait(false);
|
||||
@@ -1786,7 +1787,7 @@ app.MapPost("/v1/alerts/{alertId}/bundle/verify", async Task<Results<Ok<BundleVe
|
||||
{
|
||||
AlertId = alertId,
|
||||
IsValid = result.IsValid,
|
||||
VerifiedAt = DateTimeOffset.UtcNow,
|
||||
VerifiedAt = timeProvider.GetUtcNow(),
|
||||
SignatureValid = result.SignatureValid,
|
||||
HashValid = result.HashValid,
|
||||
ChainValid = result.ChainValid,
|
||||
|
||||
@@ -11,13 +11,16 @@ public sealed class EvidenceGraphBuilder : IEvidenceGraphBuilder
|
||||
{
|
||||
private readonly IEvidenceRepository _evidenceRepo;
|
||||
private readonly IAttestationVerifier _attestationVerifier;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public EvidenceGraphBuilder(
|
||||
IEvidenceRepository evidenceRepo,
|
||||
IAttestationVerifier attestationVerifier)
|
||||
IAttestationVerifier attestationVerifier,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_evidenceRepo = evidenceRepo;
|
||||
_attestationVerifier = attestationVerifier;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<EvidenceGraphResponse?> BuildAsync(
|
||||
@@ -126,7 +129,7 @@ public sealed class EvidenceGraphBuilder : IEvidenceGraphBuilder
|
||||
Nodes = nodes,
|
||||
Edges = edges,
|
||||
RootNodeId = verdictNode.Id,
|
||||
GeneratedAt = DateTimeOffset.UtcNow
|
||||
GeneratedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class FindingWorkflowService : IFindingWorkflowService
|
||||
|
||||
var payload = CreateBasePayload(request);
|
||||
payload["action"] = "assign";
|
||||
payload["assignee"] = BuildAssigneeNode(request.Assignee);
|
||||
payload["assignee"] = BuildAssigneeNode(request.Assignee!);
|
||||
AddComment(payload, request.Comment);
|
||||
ApplyStatus(payload, request.Status);
|
||||
ApplyAttachments(payload, request.Attachments);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Models/StellaOps.Notify.Models.csproj" />
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Persistence/StellaOps.Notify.Persistence.csproj" />
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Queue/StellaOps.Notify.Queue.csproj" />
|
||||
<ProjectReference Include="../../../Notify/__Libraries/StellaOps.Notify.Engine/StellaOps.Notify.Engine.csproj" />
|
||||
<ProjectReference Include="../../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,6 +9,12 @@ namespace StellaOps.Notify.Storage.InMemory.Repositories;
|
||||
public sealed class NotifyChannelRepositoryAdapter : INotifyChannelRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyChannelDocument> _channels = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyChannelRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyChannelDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -34,7 +40,7 @@ public sealed class NotifyChannelRepositoryAdapter : INotifyChannelRepository
|
||||
|
||||
public Task<NotifyChannelDocument> UpsertAsync(NotifyChannelDocument channel, CancellationToken cancellationToken = default)
|
||||
{
|
||||
channel.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
channel.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{channel.TenantId}:{channel.Id}";
|
||||
_channels[key] = channel;
|
||||
return Task.FromResult(channel);
|
||||
@@ -59,6 +65,12 @@ public sealed class NotifyChannelRepositoryAdapter : INotifyChannelRepository
|
||||
public sealed class NotifyRuleRepositoryAdapter : INotifyRuleRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyRuleDocument> _rules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyRuleRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyRuleDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -83,7 +95,7 @@ public sealed class NotifyRuleRepositoryAdapter : INotifyRuleRepository
|
||||
|
||||
public Task<NotifyRuleDocument> UpsertAsync(NotifyRuleDocument rule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
rule.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
rule.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{rule.TenantId}:{rule.Id}";
|
||||
_rules[key] = rule;
|
||||
return Task.FromResult(rule);
|
||||
@@ -108,6 +120,12 @@ public sealed class NotifyRuleRepositoryAdapter : INotifyRuleRepository
|
||||
public sealed class NotifyTemplateRepositoryAdapter : INotifyTemplateRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyTemplateDocument> _templates = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyTemplateRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyTemplateDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -130,7 +148,7 @@ public sealed class NotifyTemplateRepositoryAdapter : INotifyTemplateRepository
|
||||
|
||||
public Task<NotifyTemplateDocument> UpsertAsync(NotifyTemplateDocument template, CancellationToken cancellationToken = default)
|
||||
{
|
||||
template.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
template.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{template.TenantId}:{template.Id}";
|
||||
_templates[key] = template;
|
||||
return Task.FromResult(template);
|
||||
@@ -149,6 +167,12 @@ public sealed class NotifyTemplateRepositoryAdapter : INotifyTemplateRepository
|
||||
public sealed class NotifyDeliveryRepositoryAdapter : INotifyDeliveryRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyDeliveryDocument> _deliveries = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyDeliveryRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyDeliveryDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -166,7 +190,7 @@ public sealed class NotifyDeliveryRepositoryAdapter : INotifyDeliveryRepository
|
||||
|
||||
public Task<NotifyDeliveryDocument> UpsertAsync(NotifyDeliveryDocument delivery, CancellationToken cancellationToken = default)
|
||||
{
|
||||
delivery.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
delivery.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{delivery.TenantId}:{delivery.Id}";
|
||||
_deliveries[key] = delivery;
|
||||
return Task.FromResult(delivery);
|
||||
@@ -179,7 +203,7 @@ public sealed class NotifyDeliveryRepositoryAdapter : INotifyDeliveryRepository
|
||||
{
|
||||
doc.Status = status;
|
||||
doc.Error = error;
|
||||
doc.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
doc.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
return Task.FromResult(false);
|
||||
@@ -199,6 +223,12 @@ public sealed class NotifyDeliveryRepositoryAdapter : INotifyDeliveryRepository
|
||||
public sealed class NotifyDigestRepositoryAdapter : INotifyDigestRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyDigestDocument> _digests = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyDigestRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyDigestDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -209,7 +239,7 @@ public sealed class NotifyDigestRepositoryAdapter : INotifyDigestRepository
|
||||
|
||||
public Task<NotifyDigestDocument> UpsertAsync(NotifyDigestDocument digest, CancellationToken cancellationToken = default)
|
||||
{
|
||||
digest.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
digest.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{digest.TenantId}:{digest.Id}";
|
||||
_digests[key] = digest;
|
||||
return Task.FromResult(digest);
|
||||
@@ -257,10 +287,16 @@ public sealed class NotifyAuditRepositoryAdapter : INotifyAuditRepository
|
||||
public sealed class NotifyLockRepositoryAdapter : INotifyLockRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, (string Owner, DateTimeOffset ExpiresAt)> _locks = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyLockRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<bool> TryAcquireAsync(string lockKey, string owner, TimeSpan ttl, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
|
||||
// Clean up expired locks
|
||||
foreach (var key in _locks.Keys.ToList())
|
||||
@@ -288,7 +324,7 @@ public sealed class NotifyLockRepositoryAdapter : INotifyLockRepository
|
||||
{
|
||||
if (_locks.TryGetValue(lockKey, out var value) && value.Owner == owner)
|
||||
{
|
||||
var newExpiry = DateTimeOffset.UtcNow + ttl;
|
||||
var newExpiry = _timeProvider.GetUtcNow() + ttl;
|
||||
_locks[lockKey] = (owner, newExpiry);
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
@@ -302,6 +338,12 @@ public sealed class NotifyLockRepositoryAdapter : INotifyLockRepository
|
||||
public sealed class NotifyEscalationPolicyRepositoryAdapter : INotifyEscalationPolicyRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyEscalationPolicyDocument> _policies = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyEscalationPolicyRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyEscalationPolicyDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -318,7 +360,7 @@ public sealed class NotifyEscalationPolicyRepositoryAdapter : INotifyEscalationP
|
||||
|
||||
public Task<NotifyEscalationPolicyDocument> UpsertAsync(NotifyEscalationPolicyDocument policy, CancellationToken cancellationToken = default)
|
||||
{
|
||||
policy.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
policy.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{policy.TenantId}:{policy.Id}";
|
||||
_policies[key] = policy;
|
||||
return Task.FromResult(policy);
|
||||
@@ -331,6 +373,12 @@ public sealed class NotifyEscalationPolicyRepositoryAdapter : INotifyEscalationP
|
||||
public sealed class NotifyEscalationStateRepositoryAdapter : INotifyEscalationStateRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyEscalationStateDocument> _states = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyEscalationStateRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyEscalationStateDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -341,7 +389,7 @@ public sealed class NotifyEscalationStateRepositoryAdapter : INotifyEscalationSt
|
||||
|
||||
public Task<NotifyEscalationStateDocument> UpsertAsync(NotifyEscalationStateDocument state, CancellationToken cancellationToken = default)
|
||||
{
|
||||
state.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
state.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{state.TenantId}:{state.Id}";
|
||||
_states[key] = state;
|
||||
return Task.FromResult(state);
|
||||
@@ -360,6 +408,12 @@ public sealed class NotifyEscalationStateRepositoryAdapter : INotifyEscalationSt
|
||||
public sealed class NotifyOnCallScheduleRepositoryAdapter : INotifyOnCallScheduleRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyOnCallScheduleDocument> _schedules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyOnCallScheduleRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyOnCallScheduleDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -376,7 +430,7 @@ public sealed class NotifyOnCallScheduleRepositoryAdapter : INotifyOnCallSchedul
|
||||
|
||||
public Task<NotifyOnCallScheduleDocument> UpsertAsync(NotifyOnCallScheduleDocument schedule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
schedule.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
schedule.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{schedule.TenantId}:{schedule.Id}";
|
||||
_schedules[key] = schedule;
|
||||
return Task.FromResult(schedule);
|
||||
@@ -397,6 +451,12 @@ public sealed class NotifyOnCallScheduleRepositoryAdapter : INotifyOnCallSchedul
|
||||
public sealed class NotifyQuietHoursRepositoryAdapter : INotifyQuietHoursRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyQuietHoursDocument> _quietHours = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyQuietHoursRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyQuietHoursDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -413,7 +473,7 @@ public sealed class NotifyQuietHoursRepositoryAdapter : INotifyQuietHoursReposit
|
||||
|
||||
public Task<NotifyQuietHoursDocument> UpsertAsync(NotifyQuietHoursDocument quietHours, CancellationToken cancellationToken = default)
|
||||
{
|
||||
quietHours.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
quietHours.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{quietHours.TenantId}:{quietHours.Id}";
|
||||
_quietHours[key] = quietHours;
|
||||
return Task.FromResult(quietHours);
|
||||
@@ -432,6 +492,12 @@ public sealed class NotifyQuietHoursRepositoryAdapter : INotifyQuietHoursReposit
|
||||
public sealed class NotifyMaintenanceWindowRepositoryAdapter : INotifyMaintenanceWindowRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyMaintenanceWindowDocument> _windows = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyMaintenanceWindowRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyMaintenanceWindowDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -454,7 +520,7 @@ public sealed class NotifyMaintenanceWindowRepositoryAdapter : INotifyMaintenanc
|
||||
|
||||
public Task<NotifyMaintenanceWindowDocument> UpsertAsync(NotifyMaintenanceWindowDocument window, CancellationToken cancellationToken = default)
|
||||
{
|
||||
window.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
window.UpdatedAt = _timeProvider.GetUtcNow();
|
||||
var key = $"{window.TenantId}:{window.Id}";
|
||||
_windows[key] = window;
|
||||
return Task.FromResult(window);
|
||||
@@ -473,6 +539,12 @@ public sealed class NotifyMaintenanceWindowRepositoryAdapter : INotifyMaintenanc
|
||||
public sealed class NotifyInboxRepositoryAdapter : INotifyInboxRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, NotifyInboxDocument> _inbox = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public NotifyInboxRepositoryAdapter(TimeProvider? timeProvider = null)
|
||||
{
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public Task<NotifyInboxDocument?> GetByIdAsync(string tenantId, string id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -502,7 +574,7 @@ public sealed class NotifyInboxRepositoryAdapter : INotifyInboxRepository
|
||||
if (_inbox.TryGetValue(key, out var doc))
|
||||
{
|
||||
doc.Read = true;
|
||||
doc.ReadAt = DateTimeOffset.UtcNow;
|
||||
doc.ReadAt = _timeProvider.GetUtcNow();
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
return Task.FromResult(false);
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class LedgerExporter : ILedgerExporter
|
||||
private readonly ILedgerRepository _ledgerRepository;
|
||||
private readonly ILedgerExportRepository _exportRepository;
|
||||
private readonly ILogger<LedgerExporter> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -32,11 +33,13 @@ public sealed class LedgerExporter : ILedgerExporter
|
||||
public LedgerExporter(
|
||||
ILedgerRepository ledgerRepository,
|
||||
ILedgerExportRepository exportRepository,
|
||||
ILogger<LedgerExporter> logger)
|
||||
ILogger<LedgerExporter> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_ledgerRepository = ledgerRepository;
|
||||
_exportRepository = exportRepository;
|
||||
_logger = logger;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -44,7 +47,7 @@ public sealed class LedgerExporter : ILedgerExporter
|
||||
LedgerExport export,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startTime = DateTimeOffset.UtcNow;
|
||||
var startTime = _timeProvider.GetUtcNow();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -83,7 +86,7 @@ public sealed class LedgerExporter : ILedgerExporter
|
||||
export = export.Complete(outputUri, digest, sizeBytes, entries.Count);
|
||||
export = await _exportRepository.UpdateAsync(export, cancellationToken);
|
||||
|
||||
var duration = DateTimeOffset.UtcNow - startTime;
|
||||
var duration = _timeProvider.GetUtcNow() - startTime;
|
||||
OrchestratorMetrics.LedgerExportCompleted(export.TenantId, export.Format);
|
||||
OrchestratorMetrics.RecordLedgerExportDuration(export.TenantId, export.Format, duration.TotalSeconds);
|
||||
OrchestratorMetrics.RecordLedgerExportSize(export.TenantId, export.Format, sizeBytes);
|
||||
@@ -165,12 +168,12 @@ public sealed class LedgerExporter : ILedgerExporter
|
||||
return (content, digest);
|
||||
}
|
||||
|
||||
private static string GenerateJson(IReadOnlyList<RunLedgerEntry> entries)
|
||||
private string GenerateJson(IReadOnlyList<RunLedgerEntry> entries)
|
||||
{
|
||||
var exportData = new LedgerExportData
|
||||
{
|
||||
SchemaVersion = "1.0.0",
|
||||
ExportedAt = DateTimeOffset.UtcNow,
|
||||
ExportedAt = _timeProvider.GetUtcNow(),
|
||||
EntryCount = entries.Count,
|
||||
Entries = entries.Select(MapEntry).ToList()
|
||||
};
|
||||
|
||||
@@ -56,15 +56,18 @@ public sealed class PostgresDuplicateSuppressor : IDuplicateSuppressor
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly string _tenantId;
|
||||
private readonly ILogger<PostgresDuplicateSuppressor> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresDuplicateSuppressor(
|
||||
OrchestratorDataSource dataSource,
|
||||
string tenantId,
|
||||
ILogger<PostgresDuplicateSuppressor> logger)
|
||||
ILogger<PostgresDuplicateSuppressor> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_tenantId = tenantId ?? throw new ArgumentNullException(nameof(tenantId));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<bool> HasProcessedAsync(string scopeKey, string eventKey, CancellationToken cancellationToken)
|
||||
@@ -125,7 +128,7 @@ public sealed class PostgresDuplicateSuppressor : IDuplicateSuppressor
|
||||
command.Parameters.AddWithValue("event_key", eventKey);
|
||||
command.Parameters.AddWithValue("event_time", eventTime);
|
||||
command.Parameters.AddWithValue("batch_id", (object?)batchId ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("expires_at", DateTimeOffset.UtcNow + ttl);
|
||||
command.Parameters.AddWithValue("expires_at", _timeProvider.GetUtcNow() + ttl);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -143,7 +146,7 @@ public sealed class PostgresDuplicateSuppressor : IDuplicateSuppressor
|
||||
return;
|
||||
}
|
||||
|
||||
var expiresAt = DateTimeOffset.UtcNow + ttl;
|
||||
var expiresAt = _timeProvider.GetUtcNow() + ttl;
|
||||
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(_tenantId, "writer", cancellationToken).ConfigureAwait(false);
|
||||
await using var transaction = await connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -108,13 +108,16 @@ public sealed class PostgresJobRepository : IJobRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresJobRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresJobRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresJobRepository> logger)
|
||||
ILogger<PostgresJobRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Job?> GetByIdAsync(string tenantId, Guid jobId, CancellationToken cancellationToken)
|
||||
@@ -228,8 +231,8 @@ public sealed class PostgresJobRepository : IJobRepository
|
||||
command.Parameters.AddWithValue("lease_id", leaseId);
|
||||
command.Parameters.AddWithValue("worker_id", workerId);
|
||||
command.Parameters.AddWithValue("lease_until", leaseUntil);
|
||||
command.Parameters.AddWithValue("leased_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("leased_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
if (jobType != null)
|
||||
{
|
||||
@@ -263,7 +266,7 @@ public sealed class PostgresJobRepository : IJobRepository
|
||||
command.Parameters.AddWithValue("job_id", jobId);
|
||||
command.Parameters.AddWithValue("lease_id", leaseId);
|
||||
command.Parameters.AddWithValue("new_lease_until", newLeaseUntil);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
return rows > 0;
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed class PostgresPackRegistryRepository : IPackRegistryRepository
|
||||
{
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresPackRegistryRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
private const string PackColumns = """
|
||||
pack_id, tenant_id, project_id, name, display_name, description,
|
||||
@@ -33,10 +34,12 @@ public sealed class PostgresPackRegistryRepository : IPackRegistryRepository
|
||||
|
||||
public PostgresPackRegistryRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresPackRegistryRepository> logger)
|
||||
ILogger<PostgresPackRegistryRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
// Pack CRUD
|
||||
@@ -264,7 +267,7 @@ public sealed class PostgresPackRegistryRepository : IPackRegistryRepository
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("pack_id", packId);
|
||||
command.Parameters.AddWithValue("status", status.ToString().ToLowerInvariant());
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow.UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow().UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
command.Parameters.AddWithValue("published_at", (object?)publishedAt?.UtcDateTime ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("published_by", (object?)publishedBy ?? DBNull.Value);
|
||||
@@ -534,7 +537,7 @@ public sealed class PostgresPackRegistryRepository : IPackRegistryRepository
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("pack_version_id", packVersionId);
|
||||
command.Parameters.AddWithValue("status", status.ToString().ToLowerInvariant());
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow.UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow().UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
command.Parameters.AddWithValue("published_at", (object?)publishedAt?.UtcDateTime ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("published_by", (object?)publishedBy ?? DBNull.Value);
|
||||
@@ -574,7 +577,7 @@ public sealed class PostgresPackRegistryRepository : IPackRegistryRepository
|
||||
command.Parameters.AddWithValue("signature_algorithm", signatureAlgorithm);
|
||||
command.Parameters.AddWithValue("signed_by", signedBy);
|
||||
command.Parameters.AddWithValue("signed_at", signedAt.UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow.UtcDateTime);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow().UtcDateTime);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@@ -128,11 +128,16 @@ public sealed class PostgresPackRunRepository : IPackRunRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresPackRunRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresPackRunRepository(OrchestratorDataSource dataSource, ILogger<PostgresPackRunRepository> logger)
|
||||
public PostgresPackRunRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresPackRunRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<PackRun?> GetByIdAsync(string tenantId, Guid packRunId, CancellationToken cancellationToken)
|
||||
@@ -244,7 +249,7 @@ public sealed class PostgresPackRunRepository : IPackRunRepository
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "writer", cancellationToken).ConfigureAwait(false);
|
||||
await using var command = new NpgsqlCommand(sql, connection);
|
||||
command.CommandTimeout = _dataSource.CommandTimeoutSeconds;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var now = _timeProvider.GetUtcNow();
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("lease_id", leaseId);
|
||||
command.Parameters.AddWithValue("task_runner_id", taskRunnerId);
|
||||
@@ -275,7 +280,7 @@ public sealed class PostgresPackRunRepository : IPackRunRepository
|
||||
command.Parameters.AddWithValue("pack_run_id", packRunId);
|
||||
command.Parameters.AddWithValue("lease_id", leaseId);
|
||||
command.Parameters.AddWithValue("new_lease_until", newLeaseUntil);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
return rows > 0;
|
||||
@@ -292,7 +297,7 @@ public sealed class PostgresPackRunRepository : IPackRunRepository
|
||||
command.Parameters.AddWithValue("lease_id", leaseId);
|
||||
command.Parameters.AddWithValue("status", StatusToString(newStatus));
|
||||
command.Parameters.AddWithValue("reason", (object?)reason ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("completed_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("completed_at", _timeProvider.GetUtcNow());
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -113,13 +113,16 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresQuotaRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresQuotaRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresQuotaRepository> logger)
|
||||
ILogger<PostgresQuotaRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Quota?> GetByIdAsync(string tenantId, Guid quotaId, CancellationToken cancellationToken)
|
||||
@@ -229,7 +232,7 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
command.Parameters.AddWithValue("current_active", currentActive);
|
||||
command.Parameters.AddWithValue("current_hour_count", currentHourCount);
|
||||
command.Parameters.AddWithValue("current_hour_start", currentHourStart);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -245,7 +248,7 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
command.Parameters.AddWithValue("quota_id", quotaId);
|
||||
command.Parameters.AddWithValue("pause_reason", reason);
|
||||
command.Parameters.AddWithValue("quota_ticket", (object?)ticket ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -263,7 +266,7 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("quota_id", quotaId);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -281,7 +284,7 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("quota_id", quotaId);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -294,7 +297,7 @@ public sealed class PostgresQuotaRepository : IQuotaRepository
|
||||
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("quota_id", quotaId);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -69,13 +69,16 @@ public sealed class PostgresRunRepository : IRunRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresRunRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresRunRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresRunRepository> logger)
|
||||
ILogger<PostgresRunRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Run?> GetByIdAsync(string tenantId, Guid runId, CancellationToken cancellationToken)
|
||||
@@ -149,7 +152,7 @@ public sealed class PostgresRunRepository : IRunRepository
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("run_id", runId);
|
||||
command.Parameters.AddWithValue("succeeded", succeeded);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
|
||||
|
||||
@@ -74,13 +74,16 @@ public sealed class PostgresSourceRepository : ISourceRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresSourceRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresSourceRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresSourceRepository> logger)
|
||||
ILogger<PostgresSourceRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Source?> GetByIdAsync(string tenantId, Guid sourceId, CancellationToken cancellationToken)
|
||||
@@ -175,7 +178,7 @@ public sealed class PostgresSourceRepository : ISourceRepository
|
||||
command.Parameters.AddWithValue("source_id", sourceId);
|
||||
command.Parameters.AddWithValue("pause_reason", reason);
|
||||
command.Parameters.AddWithValue("pause_ticket", (object?)ticket ?? DBNull.Value);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -193,7 +196,7 @@ public sealed class PostgresSourceRepository : ISourceRepository
|
||||
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("source_id", sourceId);
|
||||
command.Parameters.AddWithValue("updated_at", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("updated_at", _timeProvider.GetUtcNow());
|
||||
command.Parameters.AddWithValue("updated_by", updatedBy);
|
||||
|
||||
var rows = await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -77,13 +77,16 @@ public sealed class PostgresThrottleRepository : IThrottleRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresThrottleRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresThrottleRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresThrottleRepository> logger)
|
||||
ILogger<PostgresThrottleRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Throttle?> GetByIdAsync(string tenantId, Guid throttleId, CancellationToken cancellationToken)
|
||||
@@ -110,7 +113,7 @@ public sealed class PostgresThrottleRepository : IThrottleRepository
|
||||
command.CommandTimeout = _dataSource.CommandTimeoutSeconds;
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("source_id", sourceId);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
var throttles = new List<Throttle>();
|
||||
@@ -128,7 +131,7 @@ public sealed class PostgresThrottleRepository : IThrottleRepository
|
||||
command.CommandTimeout = _dataSource.CommandTimeoutSeconds;
|
||||
command.Parameters.AddWithValue("tenant_id", tenantId);
|
||||
command.Parameters.AddWithValue("job_type", jobType);
|
||||
command.Parameters.AddWithValue("now", DateTimeOffset.UtcNow);
|
||||
command.Parameters.AddWithValue("now", _timeProvider.GetUtcNow());
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
var throttles = new List<Throttle>();
|
||||
|
||||
@@ -100,13 +100,16 @@ public sealed class PostgresWatermarkRepository : IWatermarkRepository
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly ILogger<PostgresWatermarkRepository> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public PostgresWatermarkRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
ILogger<PostgresWatermarkRepository> logger)
|
||||
ILogger<PostgresWatermarkRepository> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
public async Task<Watermark?> GetByScopeKeyAsync(string tenantId, string scopeKey, CancellationToken cancellationToken)
|
||||
@@ -271,7 +274,7 @@ public sealed class PostgresWatermarkRepository : IWatermarkRepository
|
||||
int limit,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var thresholdTime = DateTimeOffset.UtcNow - lagThreshold;
|
||||
var thresholdTime = _timeProvider.GetUtcNow() - lagThreshold;
|
||||
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "reader", cancellationToken).ConfigureAwait(false);
|
||||
await using var command = new NpgsqlCommand(SelectLaggingSql, connection);
|
||||
|
||||
@@ -152,7 +152,8 @@ public sealed record BackfillCheckpoint(
|
||||
int batchNumber,
|
||||
DateTimeOffset batchStart,
|
||||
DateTimeOffset batchEnd,
|
||||
int eventsInBatch)
|
||||
int eventsInBatch,
|
||||
DateTimeOffset startedAt)
|
||||
{
|
||||
return new BackfillCheckpoint(
|
||||
CheckpointId: Guid.NewGuid(),
|
||||
@@ -166,7 +167,7 @@ public sealed record BackfillCheckpoint(
|
||||
EventsSkipped: 0,
|
||||
EventsFailed: 0,
|
||||
BatchHash: null,
|
||||
StartedAt: DateTimeOffset.UtcNow,
|
||||
StartedAt: startedAt,
|
||||
CompletedAt: null,
|
||||
ErrorMessage: null);
|
||||
}
|
||||
@@ -174,7 +175,7 @@ public sealed record BackfillCheckpoint(
|
||||
/// <summary>
|
||||
/// Marks the checkpoint as complete.
|
||||
/// </summary>
|
||||
public BackfillCheckpoint Complete(int processed, int skipped, int failed, string? batchHash)
|
||||
public BackfillCheckpoint Complete(int processed, int skipped, int failed, string? batchHash, DateTimeOffset completedAt)
|
||||
{
|
||||
return this with
|
||||
{
|
||||
@@ -182,18 +183,18 @@ public sealed record BackfillCheckpoint(
|
||||
EventsSkipped = skipped,
|
||||
EventsFailed = failed,
|
||||
BatchHash = batchHash,
|
||||
CompletedAt = DateTimeOffset.UtcNow
|
||||
CompletedAt = completedAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the checkpoint as failed.
|
||||
/// </summary>
|
||||
public BackfillCheckpoint Fail(string error)
|
||||
public BackfillCheckpoint Fail(string error, DateTimeOffset completedAt)
|
||||
{
|
||||
return this with
|
||||
{
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
CompletedAt = completedAt,
|
||||
ErrorMessage = error
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,15 +14,18 @@ public sealed class FirstSignalSnapshotWriter : BackgroundService
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly FirstSignalSnapshotWriterOptions _options;
|
||||
private readonly ILogger<FirstSignalSnapshotWriter> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public FirstSignalSnapshotWriter(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IOptions<FirstSignalOptions> options,
|
||||
ILogger<FirstSignalSnapshotWriter> logger)
|
||||
ILogger<FirstSignalSnapshotWriter> logger,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||
_options = (options ?? throw new ArgumentNullException(nameof(options))).Value.SnapshotWriter;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
@@ -76,7 +79,7 @@ public sealed class FirstSignalSnapshotWriter : BackgroundService
|
||||
var runRepository = scope.ServiceProvider.GetRequiredService<IRunRepository>();
|
||||
var firstSignalService = scope.ServiceProvider.GetRequiredService<CoreServices.IFirstSignalService>();
|
||||
|
||||
var createdAfter = DateTimeOffset.UtcNow.Subtract(lookback);
|
||||
var createdAfter = _timeProvider.GetUtcNow().Subtract(lookback);
|
||||
|
||||
var pending = await runRepository.ListAsync(
|
||||
tenantId,
|
||||
|
||||
@@ -36,13 +36,14 @@ public static class HealthEndpoints
|
||||
return app;
|
||||
}
|
||||
|
||||
private static IResult GetHealth()
|
||||
private static IResult GetHealth([FromServices] TimeProvider timeProvider)
|
||||
{
|
||||
return Results.Ok(new HealthResponse("ok", DateTimeOffset.UtcNow));
|
||||
return Results.Ok(new HealthResponse("ok", timeProvider.GetUtcNow()));
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetReadiness(
|
||||
[FromServices] OrchestratorDataSource dataSource,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
@@ -53,14 +54,14 @@ public static class HealthEndpoints
|
||||
if (!dbHealthy)
|
||||
{
|
||||
return Results.Json(
|
||||
new ReadinessResponse("not_ready", DateTimeOffset.UtcNow, new Dictionary<string, string>
|
||||
new ReadinessResponse("not_ready", timeProvider.GetUtcNow(), new Dictionary<string, string>
|
||||
{
|
||||
["database"] = "unhealthy"
|
||||
}),
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
return Results.Ok(new ReadinessResponse("ready", DateTimeOffset.UtcNow, new Dictionary<string, string>
|
||||
return Results.Ok(new ReadinessResponse("ready", timeProvider.GetUtcNow(), new Dictionary<string, string>
|
||||
{
|
||||
["database"] = "healthy"
|
||||
}));
|
||||
@@ -68,7 +69,7 @@ public static class HealthEndpoints
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Results.Json(
|
||||
new ReadinessResponse("not_ready", DateTimeOffset.UtcNow, new Dictionary<string, string>
|
||||
new ReadinessResponse("not_ready", timeProvider.GetUtcNow(), new Dictionary<string, string>
|
||||
{
|
||||
["database"] = $"error: {ex.Message}"
|
||||
}),
|
||||
@@ -76,14 +77,15 @@ public static class HealthEndpoints
|
||||
}
|
||||
}
|
||||
|
||||
private static IResult GetLiveness()
|
||||
private static IResult GetLiveness([FromServices] TimeProvider timeProvider)
|
||||
{
|
||||
// Liveness just checks the process is alive
|
||||
return Results.Ok(new HealthResponse("alive", DateTimeOffset.UtcNow));
|
||||
return Results.Ok(new HealthResponse("alive", timeProvider.GetUtcNow()));
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetHealthDetails(
|
||||
[FromServices] OrchestratorDataSource dataSource,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var checks = new Dictionary<string, HealthCheckResult>();
|
||||
@@ -96,12 +98,12 @@ public static class HealthEndpoints
|
||||
checks["database"] = new HealthCheckResult(
|
||||
dbHealthy ? "healthy" : "unhealthy",
|
||||
dbHealthy ? null : "Connection test failed",
|
||||
DateTimeOffset.UtcNow);
|
||||
timeProvider.GetUtcNow());
|
||||
overallHealthy &= dbHealthy;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
checks["database"] = new HealthCheckResult("unhealthy", ex.Message, DateTimeOffset.UtcNow);
|
||||
checks["database"] = new HealthCheckResult("unhealthy", ex.Message, timeProvider.GetUtcNow());
|
||||
overallHealthy = false;
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ public static class HealthEndpoints
|
||||
checks["memory"] = new HealthCheckResult(
|
||||
memoryHealthy ? "healthy" : "degraded",
|
||||
$"Used: {memoryUsedMb:F2} MB",
|
||||
DateTimeOffset.UtcNow);
|
||||
timeProvider.GetUtcNow());
|
||||
|
||||
// Thread pool check
|
||||
ThreadPool.GetAvailableThreads(out var workerThreads, out var completionPortThreads);
|
||||
@@ -124,11 +126,11 @@ public static class HealthEndpoints
|
||||
checks["threadPool"] = new HealthCheckResult(
|
||||
threadPoolHealthy ? "healthy" : "degraded",
|
||||
$"Worker threads available: {workerThreads}/{maxWorkerThreads}",
|
||||
DateTimeOffset.UtcNow);
|
||||
timeProvider.GetUtcNow());
|
||||
|
||||
var response = new HealthDetailsResponse(
|
||||
overallHealthy ? "healthy" : "unhealthy",
|
||||
DateTimeOffset.UtcNow,
|
||||
timeProvider.GetUtcNow(),
|
||||
checks);
|
||||
|
||||
return overallHealthy
|
||||
|
||||
@@ -55,10 +55,12 @@ public static class KpiEndpoints
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] string? tenant,
|
||||
[FromServices] IKpiCollector collector,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var start = from ?? DateTimeOffset.UtcNow.AddDays(-7);
|
||||
var end = to ?? DateTimeOffset.UtcNow;
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var start = from ?? now.AddDays(-7);
|
||||
var end = to ?? now;
|
||||
|
||||
var kpis = await collector.CollectAsync(start, end, tenant, ct);
|
||||
return Results.Ok(kpis);
|
||||
@@ -69,11 +71,13 @@ public static class KpiEndpoints
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] string? tenant,
|
||||
[FromServices] IKpiCollector collector,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var kpis = await collector.CollectAsync(
|
||||
from ?? DateTimeOffset.UtcNow.AddDays(-7),
|
||||
to ?? DateTimeOffset.UtcNow,
|
||||
from ?? now.AddDays(-7),
|
||||
to ?? now,
|
||||
tenant,
|
||||
ct);
|
||||
return Results.Ok(kpis.Reachability);
|
||||
@@ -84,11 +88,13 @@ public static class KpiEndpoints
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] string? tenant,
|
||||
[FromServices] IKpiCollector collector,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var kpis = await collector.CollectAsync(
|
||||
from ?? DateTimeOffset.UtcNow.AddDays(-7),
|
||||
to ?? DateTimeOffset.UtcNow,
|
||||
from ?? now.AddDays(-7),
|
||||
to ?? now,
|
||||
tenant,
|
||||
ct);
|
||||
return Results.Ok(kpis.Explainability);
|
||||
@@ -99,11 +105,13 @@ public static class KpiEndpoints
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] string? tenant,
|
||||
[FromServices] IKpiCollector collector,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var kpis = await collector.CollectAsync(
|
||||
from ?? DateTimeOffset.UtcNow.AddDays(-7),
|
||||
to ?? DateTimeOffset.UtcNow,
|
||||
from ?? now.AddDays(-7),
|
||||
to ?? now,
|
||||
tenant,
|
||||
ct);
|
||||
return Results.Ok(kpis.Runtime);
|
||||
@@ -114,11 +122,13 @@ public static class KpiEndpoints
|
||||
[FromQuery] DateTimeOffset? to,
|
||||
[FromQuery] string? tenant,
|
||||
[FromServices] IKpiCollector collector,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var kpis = await collector.CollectAsync(
|
||||
from ?? DateTimeOffset.UtcNow.AddDays(-7),
|
||||
to ?? DateTimeOffset.UtcNow,
|
||||
from ?? now.AddDays(-7),
|
||||
to ?? now,
|
||||
tenant,
|
||||
ct);
|
||||
return Results.Ok(kpis.Replay);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user