docs re-org, audit fixes, build fixes

This commit is contained in:
StellaOps Bot
2026-01-05 09:35:33 +02:00
parent eca4e964d3
commit dfab8a29c3
173 changed files with 1276 additions and 560 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ Approval is recorded via Git forge review or a signed commit trailer
* Every tag is **cosigned 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 outofband `x.y.zhotfix` 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 |
---

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -383,7 +383,7 @@ Produce **entrypointaware differential SBOMs** and continually **reenrich*
{
"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"],

View 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

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

View 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/)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,22 +3,22 @@
Deployment, runtime operations, and air-gap playbooks for running StellaOps 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -73,6 +73,7 @@ public sealed class CachePerformanceBenchmarkTests : IAsyncLifetime
_cacheService = new ValkeyAdvisoryCacheService(
_connectionFactory,
options,
metrics: null,
NullLogger<ValkeyAdvisoryCacheService>.Instance);
await ValueTask.CompletedTask;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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