up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 00:20:26 +02:00
parent e1f1bef4c1
commit 564df71bfb
2376 changed files with 334389 additions and 328032 deletions

View File

@@ -18,7 +18,8 @@
* **Per-layer caching.** Cache fragments by **layer digest** and compose image SBOMs via **CycloneDX BOM-Link** / **SPDX ExternalRef**.
* **Inventory vs Usage.** Always record the full **inventory** of what exists; separately present **usage** (entrypoint closure + loaded libs).
* **Backend decides.** PASS/FAIL is produced by **Policy** + **VEX** + **Advisories**. The scanner reports facts.
* **Attest or it didnt happen.** Every export is signed as **in-toto/DSSE** and logged in **Rekor v2**.
* **VEX-first triage UX.** Operators triage by artifact with evidence-first cards, VEX decisioning, and immutable audit bundles; see `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`.
* **Attest or it didn't happen.** Every export is signed as **in-toto/DSSE** and logged in **Rekor v2**.
* **Hybrid reachability attestations.** Every reachability graph ships with a graph-level DSSE (mandatory) plus optional edge-bundle DSSEs for runtime/init/contested edges; Policy/Signals consume graph DSSE as baseline and edge bundles for quarantine/disputes.
* **Sovereign-ready.** Cloud is used only for licensing and optional endorsement; everything else is first-party and self-hostable.
* **Competitive clarity.** Moats: deterministic replay, hybrid reachability proofs, lattice VEX, sovereign crypto, proof graph; see `docs/market/competitive-landscape.md`.
@@ -46,7 +47,7 @@
| **Attestor** | `stellaops/attestor` | Posts DSSE bundles to **Rekor v2**; verification endpoints. | Stateless; HPA by QPS. |
| **Authority** | `stellaops/authority` | Onprem OIDC issuing **shortlived OpToks** with DPoP/mTLS sender constraint. | HA behind LB. |
| **Zastava** (Runtime) | `stellaops/zastava` | Runtime inspector/enforcer (observer + optional Admission Webhook). | DaemonSet + Webhook. |
| **Web UI** | `stellaops/ui` | Angular app for scans, diffs, policy, VEX, **Scheduler**, **Notify**, runtime, reports. | Stateless. |
| **Web UI** | `stellaops/ui` | Angular app for scans, diffs, policy, VEX, vulnerability triage (artifact-first), audit bundles, **Scheduler**, **Notify**, runtime, reports. | Stateless. |
| **StellaOps.Cli** | `stellaops/cli` | CLI for init/scan/export/diff/policy/report/verify; Buildx helper; **schedule** and **notify** verbs. | Local/CI. |
### 1.2 Thirdparty (selfhosted)

View File

@@ -6,7 +6,7 @@
- **Working directory:** `src/Web/StellaOps.Web`
## Dependencies & Concurrency
- Upstream sprints: SPRINT_0209_0001_0001_ui_i (UI I), SPRINT_0210_0001_0002_ui_ii (UI II - VEX tab).
- Upstream sprints (archived): `docs/implplan/archived/SPRINT_0209_0001_0001_ui_i.md` (UI I), `docs/implplan/archived/SPRINT_0210_0001_0002_ui_ii.md` (UI II - VEX tab).
- Backend dependencies: Vuln Explorer APIs (`/v1/findings`, `/v1/vex-decisions`), Attestor service, Export Center.
- Parallel tracks: Can run alongside UI II/III for shared component work.
- Blockers to flag: VEX decision API schema finalization, Attestation viewer predicates.
@@ -18,59 +18,58 @@
- `docs/modules/ui/architecture.md`
- `docs/modules/vuln-explorer/architecture.md`
- `docs/modules/vex-lens/architecture.md`
- `docs/product-advisories/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md` (canonical)
- `docs/product-advisories/27-Nov-2025 - Explainability Layer for Vulnerability Verdicts.md`
- `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md` (canonical)
- `docs/product-advisories/archived/27-Nov-2025-superseded/27-Nov-2025 - Explainability Layer for Vulnerability Verdicts.md`
- `docs/schemas/vex-decision.schema.json`
- `docs/schemas/audit-bundle-index.schema.json`
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 1 | UI-TRIAGE-01-001 | TODO | Path corrected; work in `src/Web/StellaOps.Web` | UI Guild (src/Web/StellaOps.Web) | Create Artifacts List view with columns: Artifact, Type, Environment(s), Open/Total vulns, Max severity, Attestations badge, Last scan. Include sorting, filtering, and "View vulnerabilities" primary action. |
| 2 | UI-TRIAGE-01-002 | TODO | Depends on task 1 | UI Guild (src/Web/StellaOps.Web) | Build Vulnerability Workspace split layout: left panel with finding cards (CVE, package, severity, path), right panel with Explainability tabs (Overview, Reachability, Policy, Attestations). |
| 3 | UI-TRIAGE-01-003 | TODO | Depends on task 2 | UI Guild (src/Web/StellaOps.Web) | Implement evidence-first Finding Card component with severity badge, package info, location path, and primary actions (Fix PR, VEX, Attach Evidence). Include `New`, `VEX: Not affected`, `Policy: blocked` badges. |
| 4 | UI-TRIAGE-01-004 | TODO | Depends on task 3 | UI Guild (src/Web/StellaOps.Web) | Build Explainability Panel Overview tab: title, severity, package/version, scanner+DB date, finding history timeline, current VEX decision summary. |
| 5 | UI-TRIAGE-01-005 | TODO | Depends on task 4 | UI Guild (src/Web/StellaOps.Web) | Build Explainability Panel Reachability tab: call path visualization, module list, runtime usage indicators (when available from scanner). |
| 6 | UI-TRIAGE-01-006 | TODO | Depends on task 4 | UI Guild (src/Web/StellaOps.Web) | Build Explainability Panel Policy tab: policy evaluation result, gate details with "this gate failed because..." explanation, links to gate definitions. |
| 7 | UI-TRIAGE-01-007 | TODO | Depends on task 4 | UI Guild (src/Web/StellaOps.Web) | Build Explainability Panel Attestations tab: list attestations mentioning artifact/vulnerabilityId/scan with type, subject, predicate, signer, verified badge. |
| 8 | UI-VEX-02-001 | TODO | Depends on task 3 | UI Guild; Excititor Guild (src/Web/StellaOps.Web) | Create VEX Modal component with status radio buttons (Not Affected, Affected-mitigated, Affected-unmitigated, Fixed), justification type select, justification text area. |
| 9 | UI-VEX-02-002 | TODO | Depends on task 8 | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal scope section: environments multi-select, projects multi-select with clear scope preview. |
| 10 | UI-VEX-02-003 | TODO | Depends on task 9 | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal validity section: notBefore date (default now), notAfter date with expiry recommendations and warnings for long durations. |
| 11 | UI-VEX-02-004 | TODO | Depends on task 10 | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal evidence section: add links (PR, ticket, doc, commit), attach attestation picker, evidence preview list with remove action. |
| 12 | UI-VEX-02-005 | TODO | Depends on task 11 | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal review section: summary preview of VEX statement to be created, "Will generate signed attestation" indicator, View raw JSON toggle for power users. |
| 13 | UI-VEX-02-006 | TODO | Depends on task 12 | UI Guild (src/Web/StellaOps.Web) | Wire VEX Modal to backend: POST /vex-decisions on save, handle success/error states, update finding card VEX badge on completion. |
| 14 | UI-VEX-02-007 | TODO | Depends on task 13 | UI Guild (src/Web/StellaOps.Web) | Add bulk VEX action: multi-select findings from list, open VEX modal with bulk context, apply decision to all selected findings. |
| 15 | UI-ATT-03-001 | TODO | Depends on task 7 | UI Guild; Attestor Guild (src/Web/StellaOps.Web) | Create Attestations View per artifact: table with Type, Subject, Predicate type, Scanner/policy engine, Signer (keyId + trusted badge), Created at, Verified status. |
| 16 | UI-ATT-03-002 | TODO | Depends on task 15 | UI Guild (src/Web/StellaOps.Web) | Build Attestation Detail modal: header (statement id, subject, signer), predicate preview (vuln scan counts, SBOM bomRef, VEX decision status), verify command snippet. |
| 17 | UI-ATT-03-003 | TODO | Depends on task 16 | UI Guild (src/Web/StellaOps.Web) | Add "Signed evidence" pill to finding cards: clicking opens attestation detail modal, shows human-readable JSON view. |
| 18 | UI-GATE-04-001 | TODO | Depends on task 6 | UI Guild; Policy Guild (src/Web/StellaOps.Web) | Create Policy & Gating View: matrix of gates vs subject types (CI Build, Registry Admission, Runtime Admission), rule descriptions, last evaluation stats. |
| 19 | UI-GATE-04-002 | TODO | Depends on task 18 | UI Guild (src/Web/StellaOps.Web) | Add gate drill-down: recent evaluations list, artifact links, policy attestation links, condition failure explanations. |
| 20 | UI-GATE-04-003 | TODO | Depends on task 19 | UI Guild (src/Web/StellaOps.Web) | Add "Ready to deploy" badge on artifact cards when all gates pass and required attestations verified. |
| 21 | UI-AUDIT-05-001 | TODO | Depends on task 1 | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Create "Create immutable audit bundle" button on Artifact page, Pipeline run detail, and Policy evaluation detail views. |
| 22 | UI-AUDIT-05-002 | TODO | Depends on task 21 | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Build Audit Bundle creation wizard: subject artifact+digest selection, time window picker, content checklist (Vuln reports, SBOM, VEX, Policy evals, Attestations). |
| 23 | UI-AUDIT-05-003 | TODO | Depends on task 22 | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Wire audit bundle creation to POST /audit-bundles, show progress, display bundle ID, hash, download button, and OCI reference on completion. |
| 24 | UI-AUDIT-05-004 | TODO | Depends on task 23 | UI Guild (src/Web/StellaOps.Web) | Add audit bundle history view: list previously created bundles with bundleId, createdAt, subject, download/view actions. |
| 25 | API-VEX-06-001 | TODO | - | API Guild (src/VulnExplorer) | Implement POST /v1/vex-decisions endpoint with VexDecisionDto request/response per schema, validation, attestation generation trigger. |
| 26 | API-VEX-06-002 | TODO | API-VEX-06-001 | API Guild (src/VulnExplorer) | Implement PATCH /v1/vex-decisions/{id} for updating existing decisions with supersedes tracking. |
| 27 | API-VEX-06-003 | TODO | API-VEX-06-002 | API Guild (src/VulnExplorer) | Implement GET /v1/vex-decisions with filters for vulnerabilityId, subject, status, scope, validFor. |
| 28 | API-AUDIT-07-001 | TODO | - | API Guild (src/ExportCenter) | Implement POST /v1/audit-bundles endpoint with bundle creation, index generation, ZIP/OCI artifact production. |
| 29 | API-AUDIT-07-002 | TODO | API-AUDIT-07-001 | API Guild (src/ExportCenter) | Implement GET /v1/audit-bundles/{bundleId} for bundle download with integrity verification. |
| 30 | SCHEMA-08-001 | TODO | - | Platform Guild | Create docs/schemas/vex-decision.schema.json with JSON Schema 2020-12 definition per advisory. |
| 31 | SCHEMA-08-002 | TODO | SCHEMA-08-001 | Platform Guild | Create docs/schemas/attestation-vuln-scan.schema.json for vulnerability scan attestation predicate. |
| 32 | SCHEMA-08-003 | TODO | SCHEMA-08-002 | Platform Guild | Create docs/schemas/audit-bundle-index.schema.json for audit bundle manifest structure. |
| 33 | DTO-09-001 | TODO | SCHEMA-08-001 | API Guild | Create VexDecisionDto, SubjectRefDto, EvidenceRefDto, VexScopeDto, ValidForDto C# DTOs per advisory. |
| 34 | DTO-09-002 | TODO | SCHEMA-08-002 | API Guild | Create VulnScanAttestationDto, AttestationSubjectDto, VulnScanPredicateDto C# DTOs per advisory. |
| 35 | DTO-09-003 | TODO | SCHEMA-08-003 | API Guild | Create AuditBundleIndexDto, BundleArtifactDto, BundleVexDecisionEntryDto C# DTOs per advisory. |
| 36 | TS-10-001 | TODO | Schemas not present locally; path corrected to `src/Web/StellaOps.Web` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VexDecision, SubjectRef, EvidenceRef, VexScope, ValidFor per advisory. |
| 37 | TS-10-002 | TODO | Schemas not present locally; path corrected to `src/Web/StellaOps.Web` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VulnScanAttestation, AttestationSubject, VulnScanPredicate per advisory. |
| 38 | TS-10-003 | TODO | Schemas not present locally; path corrected to `src/Web/StellaOps.Web` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for AuditBundleIndex, BundleArtifact, BundleVexDecisionEntry per advisory. |
| 39 | DOC-11-001 | TODO | Product advisory doc sync | Docs Guild (docs/) | Update high-level positioning for VEX-first triage: refresh docs/key-features.md and docs/07_HIGH_LEVEL_ARCHITECTURE.md with UX/audit bundle narrative; link 28-Nov-2025 advisory. |
| 40 | DOC-11-002 | TODO | DOC-11-001 | Docs Guild; UI Guild | Update docs/modules/ui/architecture.md with triage workspace + VEX modal flows; add schema links and advisory cross-references. |
| 41 | DOC-11-003 | TODO | DOC-11-001 | Docs Guild; Vuln Explorer Guild; Export Center Guild | Update docs/modules/vuln-explorer/architecture.md and docs/modules/export-center/architecture.md with VEX decision/audit bundle API surfaces and schema references. |
| 42 | TRIAGE-GAPS-215-042 | TODO | Close VT1VT10 from `31-Nov-2025 FINDINGS.md`; depends on schema publication and UI workspace bootstrap | UI Guild · Platform Guild | Remediate VT1VT10: publish signed schemas + canonical JSON, enforce evidence linkage (graph/policy/attestations), tenant/RBAC controls, deterministic ordering/pagination, a11y standards, offline triage-kit exports, supersedes/conflict rules, attestation verification UX, redaction policy, UX telemetry/SLIs with alerts. |
| 43 | UI-PROOF-VEX-0215-010 | TODO | Proof-linked VEX UI spec; depends on VexLens/Findings APIs and DSSE headers | UI Guild; VexLens Guild; Policy Guild | Implement proof-linked Not Affected badge/drawer: scoped endpoints + tenant headers, cache/staleness policy, client integrity checks, failure/offline UX, evidence precedence, telemetry schema/privacy, signed permalinks, revision reconciliation, fixtures/tests. |
| 44 | TTE-GAPS-0215-011 | TODO | TTE metric advisory; align with telemetry core sprint | UI Guild; Telemetry Guild | Close TTE1TTE10: publish tte-event schema, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLAs, offline-kit handling, alert/runbook, release regression gate, and a11y/viewport tests. |
| 1 | UI-TRIAGE-01-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-artifacts.component.ts` | UI Guild (src/Web/StellaOps.Web) | Create Artifacts List view with columns: Artifact, Type, Environment(s), Open/Total vulns, Max severity, Attestations badge, Last scan. Include sorting, filtering, and "View vulnerabilities" primary action. |
| 2 | UI-TRIAGE-01-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.ts` | UI Guild (src/Web/StellaOps.Web) | Build Vulnerability Workspace split layout: left panel with finding cards (CVE, package, severity, path), right panel with Explainability tabs (Overview, Reachability, Policy, Attestations). |
| 3 | UI-TRIAGE-01-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web/StellaOps.Web) | Implement evidence-first Finding Card component with severity badge, package info, location path, and primary actions (Fix PR, VEX, Attach Evidence). Include `New`, `VEX: Not affected`, `Policy: blocked` badges. |
| 4 | UI-TRIAGE-01-004 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web/StellaOps.Web) | Build Explainability Panel Overview tab: title, severity, package/version, scanner+DB date, finding history timeline, current VEX decision summary. |
| 5 | UI-TRIAGE-01-005 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web.StellaOps.Web) | Build Explainability Panel Reachability tab: call path visualization, module list, runtime usage indicators (when available from scanner). |
| 6 | UI-TRIAGE-01-006 | DONE | Evidence: `src/Web.StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web.StellaOps.Web) | Build Explainability Panel Policy tab: policy evaluation result, gate details with "this gate failed because..." explanation, links to gate definitions. |
| 7 | UI-TRIAGE-01-007 | DONE | Evidence: `src/Web.StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web.StellaOps.Web) | Build Explainability Panel Attestations tab: list attestations mentioning artifact/vulnerabilityId/scan with type, subject, predicate, signer, verified badge. |
| 8 | UI-VEX-02-001 | DONE | Evidence: `src/Web.StellaOps.Web/src/app/features/triage/vex-decision-modal.component.ts` | UI Guild; Excititor Guild (src/Web.StellaOps.Web) | Create VEX Modal component with status radio buttons (Not Affected, Affected-mitigated, Affected-unmitigated, Fixed), justification type select, justification text area. |
| 9 | UI-VEX-02-002 | DONE | Evidence: `src/Web.StellaOps.Web/src/app/features/triage/vex-decision-modal.component.ts` | UI Guild (src/Web.StellaOps.Web) | Add VEX Modal scope section: environments multi-select, projects multi-select with clear scope preview. |
| 10 | UI-VEX-02-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/vex-decision-modal.component.html` | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal validity section: notBefore date (default now), notAfter date with expiry recommendations and warnings for long durations. |
| 11 | UI-VEX-02-004 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/vex-decision-modal.component.html` | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal evidence section: add links (PR, ticket, doc, commit), attach attestation picker, evidence preview list with remove action. |
| 12 | UI-VEX-02-005 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/vex-decision-modal.component.html` | UI Guild (src/Web/StellaOps.Web) | Add VEX Modal review section: summary preview of VEX statement to be created, "Will generate signed attestation" indicator, View raw JSON toggle for power users. |
| 13 | UI-VEX-02-006 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/vex-decision-modal.component.ts`; `src/Web/StellaOps.Web/src/app/core/api/vex-decisions.client.ts` | UI Guild (src/Web/StellaOps.Web) | Wire VEX Modal to backend: POST /v1/vex-decisions on save, handle success/error states, update finding card VEX badge on completion. |
| 14 | UI-VEX-02-007 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.ts`; `src/Web/StellaOps.Web/src/app/features/triage/vex-decision-modal.component.ts` | UI Guild (src/Web/StellaOps.Web) | Add bulk VEX action: multi-select findings from list, open VEX modal with bulk context, apply decision to all selected findings. |
| 15 | UI-ATT-03-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild; Attestor Guild (src/Web/StellaOps.Web) | Create Attestations View per artifact: table with Type, Subject, Predicate type, Scanner/policy engine, Signer (keyId + trusted badge), Created at, Verified status. |
| 16 | UI-ATT-03-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-attestation-detail-modal.component.ts` | UI Guild (src/Web/StellaOps.Web) | Build Attestation Detail modal: header (statement id, subject, signer), predicate preview (vuln scan counts, SBOM bomRef, VEX decision status), verify command snippet. |
| 17 | UI-ATT-03-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild (src/Web/StellaOps.Web) | Add "Signed evidence" pill to finding cards: clicking opens attestation detail modal, shows human-readable JSON view. |
| 18 | UI-GATE-04-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html` | UI Guild; Policy Guild (src/Web/StellaOps.Web) | Create Policy & Gating View: matrix of gates vs subject types (CI Build, Registry Admission, Runtime Admission), rule descriptions, last evaluation stats. |
| 19 | UI-GATE-04-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.ts` | UI Guild (src/Web/StellaOps.Web) | Add gate drill-down: recent evaluations list, artifact links, policy attestation links, condition failure explanations. |
| 20 | UI-GATE-04-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-artifacts.component.html` | UI Guild (src/Web/StellaOps.Web) | Add "Ready to deploy" badge on artifact cards when all gates pass and required attestations verified. |
| 21 | UI-AUDIT-05-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-workspace.component.html`; `src/Web/StellaOps.Web/src/app/features/orchestrator/orchestrator-job-detail.component.ts`; `src/Web/StellaOps.Web/src/app/features/policy-studio/explain/policy-explain.component.ts` | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Create "Create immutable audit bundle" button on Artifact page, Pipeline run detail, and Policy evaluation detail views. |
| 22 | UI-AUDIT-05-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundle-new.component.ts` | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Build Audit Bundle creation wizard: subject artifact+digest selection, time window picker, content checklist (Vuln reports, SBOM, VEX, Policy evals, Attestations). |
| 23 | UI-AUDIT-05-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundle-new.component.ts`; `src/Web/StellaOps.Web/src/app/core/api/audit-bundles.client.ts` | UI Guild; Export Center Guild (src/Web/StellaOps.Web) | Wire audit bundle creation to POST /v1/audit-bundles, show progress, display bundle ID, hash, download button, and OCI reference on completion. |
| 24 | UI-AUDIT-05-004 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/features/triage/triage-audit-bundles.component.ts` | UI Guild (src/Web/StellaOps.Web) | Add audit bundle history view: list previously created bundles with bundleId, createdAt, subject, download/view actions. |
| 25 | API-VEX-06-001 | BLOCKED | Blocked: needs `SCHEMA-08-001` + `DTO-09-001` sign-off/implementation in `src/VulnExplorer` | API Guild (src/VulnExplorer) | Implement POST /v1/vex-decisions endpoint with VexDecisionDto request/response per schema, validation, attestation generation trigger. |
| 26 | API-VEX-06-002 | BLOCKED | Blocked: depends on API-VEX-06-001 | API Guild (src/VulnExplorer) | Implement PATCH /v1/vex-decisions/{id} for updating existing decisions with supersedes tracking. |
| 27 | API-VEX-06-003 | BLOCKED | Blocked: depends on API-VEX-06-002 | API Guild (src/VulnExplorer) | Implement GET /v1/vex-decisions with filters for vulnerabilityId, subject, status, scope, validFor. |
| 28 | API-AUDIT-07-001 | BLOCKED | Blocked: needs `SCHEMA-08-003` + Export Center job/ZIP/OCI implementation in `src/ExportCenter` | API Guild (src/ExportCenter) | Implement POST /v1/audit-bundles endpoint with bundle creation, index generation, ZIP/OCI artifact production. |
| 29 | API-AUDIT-07-002 | BLOCKED | Blocked: depends on API-AUDIT-07-001 | API Guild (src/ExportCenter) | Implement GET /v1/audit-bundles/{bundleId} for bundle download with integrity verification. |
| 30 | SCHEMA-08-001 | BLOCKED | Blocked: Action Tracker #1 (Platform + Excititor schema review/sign-off) | Platform Guild | Review and finalize `docs/schemas/vex-decision.schema.json` (JSON Schema 2020-12) per advisory; confirm examples and versioning. |
| 31 | SCHEMA-08-002 | BLOCKED | Blocked: Action Tracker #2 (Attestor predicate review/sign-off) | Platform Guild | Review and finalize `docs/schemas/attestation-vuln-scan.schema.json` predicate schema; align predicateType URI and required fields. |
| 32 | SCHEMA-08-003 | BLOCKED | Blocked: Action Tracker #3 (Export Center format review/sign-off) | Platform Guild | Review and finalize `docs/schemas/audit-bundle-index.schema.json` for audit bundle manifest structure; confirm stable IDs and deterministic ordering guidance. |
| 33 | DTO-09-001 | BLOCKED | Blocked: depends on SCHEMA-08-001 finalization | API Guild | Create VexDecisionDto, SubjectRefDto, EvidenceRefDto, VexScopeDto, ValidForDto C# DTOs per advisory. |
| 34 | DTO-09-002 | BLOCKED | Blocked: depends on SCHEMA-08-002 finalization | API Guild | Create VulnScanAttestationDto, AttestationSubjectDto, VulnScanPredicateDto C# DTOs per advisory. |
| 35 | DTO-09-003 | BLOCKED | Blocked: depends on SCHEMA-08-003 finalization | API Guild | Create AuditBundleIndexDto, BundleArtifactDto, BundleVexDecisionEntryDto C# DTOs per advisory. |
| 36 | TS-10-001 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/evidence.models.ts`; `src/Web/StellaOps.Web/src/app/core/api/vex-decisions.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VexDecision, SubjectRef, EvidenceRef, VexScope, ValidFor per advisory. |
| 37 | TS-10-002 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/attestation-vuln-scan.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for VulnScanAttestation, AttestationSubject, VulnScanPredicate per advisory. |
| 38 | TS-10-003 | DONE | Evidence: `src/Web/StellaOps.Web/src/app/core/api/audit-bundles.models.ts` | UI Guild (src/Web/StellaOps.Web) | Create TypeScript interfaces for AuditBundleIndex, BundleArtifact, BundleVexDecisionEntry per advisory. |
| 39 | DOC-11-001 | DONE | Evidence: `docs/key-features.md`; `docs/07_HIGH_LEVEL_ARCHITECTURE.md` | Docs Guild (docs/) | Update high-level positioning for VEX-first triage: refresh docs/key-features.md and docs/07_HIGH_LEVEL_ARCHITECTURE.md with UX/audit bundle narrative; link `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`. |
| 40 | DOC-11-002 | DONE | Evidence: `docs/modules/ui/architecture.md` | Docs Guild; UI Guild | Update docs/modules/ui/architecture.md with triage workspace + VEX modal flows; add schema links and advisory cross-references. |
| 41 | DOC-11-003 | DONE | Evidence: `docs/modules/vuln-explorer/architecture.md`; `docs/modules/export-center/architecture.md` | Docs Guild; Vuln Explorer Guild; Export Center Guild | Update docs/modules/vuln-explorer/architecture.md and docs/modules/export-center/architecture.md with VEX decision/audit bundle API surfaces and schema references. |
| 42 | TRIAGE-GAPS-215-042 | BLOCKED | Blocked: depends on schema publication (`SCHEMA-08-*`) + real findings/VEX/audit APIs + telemetry contract | UI Guild · Platform Guild | Remediate VT1VT10: publish signed schemas + canonical JSON, enforce evidence linkage (graph/policy/attestations), tenant/RBAC controls, deterministic ordering/pagination, a11y standards, offline triage-kit exports, supersedes/conflict rules, attestation verification UX, redaction policy, UX telemetry/SLIs with alerts. |
| 43 | UI-PROOF-VEX-0215-010 | BLOCKED | Blocked: depends on VexLens/Findings APIs + DSSE headers + caching/integrity rules | UI Guild; VexLens Guild; Policy Guild | Implement proof-linked Not Affected badge/drawer: scoped endpoints + tenant headers, cache/staleness policy, client integrity checks, failure/offline UX, evidence precedence, telemetry schema/privacy, signed permalinks, revision reconciliation, fixtures/tests. |
| 44 | TTE-GAPS-0215-011 | BLOCKED | Blocked: depends on telemetry core sprint (TTE schema + SLIs/SLOs) | UI Guild; Telemetry Guild | Close TTE1TTE10: publish tte-event schema, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLAs, offline-kit handling, alert/runbook, release regression gate, and a11y/viewport tests. |
## Wave Coordination
- **Wave A (Schemas & DTOs):** SCHEMA-08-*, DTO-09-*, TS-10-* - Foundation work
@@ -80,7 +79,7 @@
## Wave Detail Snapshots
### Wave A - Schemas & Types
- Duration: 2-3 days
- Deliverables: JSON schemas in docs/schemas/, C# DTOs in src/VulnExplorer, TypeScript interfaces in src/UI
- Deliverables: JSON schemas in docs/schemas/, C# DTOs in src/VulnExplorer, TypeScript interfaces in src/Web/StellaOps.Web
- Exit criteria: Schemas validate, DTOs compile, TS interfaces pass type checks
### Wave B - Backend APIs
@@ -112,7 +111,8 @@
| 2 | Confirm attestation predicate types with Attestor team | API Guild | 2025-12-03 | TODO |
| 3 | Review audit bundle format with Export Center team | API Guild | 2025-12-04 | TODO |
| 4 | Accessibility review of VEX modal with Accessibility Guild | UI Guild | 2025-12-09 | TODO |
| 5 | Align UI work to canonical workspace `src/Web/StellaOps.Web`; ensure fixtures regenerated for triage/VEX components | DevEx · UI Guild | 2025-12-06 | TODO |
| 5 | Align UI work to canonical workspace `src/Web/StellaOps.Web` | DevEx · UI Guild | 2025-12-06 | DONE |
| 6 | Regenerate deterministic fixtures for triage/VEX components (tests/e2e/offline-kit) | DevEx · UI Guild | 2025-12-13 | TODO |
## Decisions & Risks
| Risk | Impact | Mitigation / Next Step |
@@ -121,20 +121,22 @@
| Attestation service not ready | UI-ATT-* tasks blocked | Mock attestation data; feature flag attestation views |
| Export Center capacity | Audit bundle generation slow | Async generation with progress; queue management |
| Bulk VEX operations performance | UI-VEX-02-007 slow for large selections | Batch API endpoint; pagination; background processing |
| Advisory doc sync lag | Docs drift from UX/API decisions | Track DOC-11-* tasks; block release sign-off until docs updated |
| UI workspace path corrected | UI-TRIAGE-* and TS-10-* tasks proceed in `src/Web/StellaOps.Web`; fixtures still needed | Keep work in canonical workspace; regenerate deterministic fixtures before merge |
| Advisory doc sync lag | Docs drift from UX/API decisions | DOC-11-* DONE; re-review docs when schemas/APIs finalize |
| UI workspace path corrected | Risk of drift if non-canonical UI workspace used | Keep work in canonical workspace `src/Web/StellaOps.Web`; regenerate deterministic fixtures before release |
| VT gaps (VT1VT10) | Missing schemas/evidence linkage/determinism/a11y/offline parity could ship broken triage UX | Track TRIAGE-GAPS-215-042; publish schemas, enforce RBAC/tenant binding, redaction, deterministic ordering, offline triage-kit, attestation verification UX, and UX telemetry before release |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-11-28 | Sprint created from product advisory `28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`. 38 tasks defined across 5 UI task groups, 2 API task groups, 3 schema tasks, 3 DTO tasks, 3 TS interface tasks. | Project mgmt |
| 2025-11-28 | Sprint created from product advisory `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`. 38 tasks defined across 5 UI task groups, 2 API task groups, 3 schema tasks, 3 DTO tasks, 3 TS interface tasks. | Project mgmt |
| 2025-11-30 | Added DOC-11-* doc-sync tasks per advisory handling rules; no scope change to delivery waves. | Project mgmt |
| 2025-11-30 | Marked UI-TRIAGE-01-001 and TS-10-* tasks BLOCKED because src/UI/StellaOps.UI lacks Angular workspace; awaiting restoration to proceed. | UI Guild |
| 2025-12-01 | Added TRIAGE-GAPS-215-042 to track VT1VT10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending schema publication and UI workspace bootstrap. | Project Mgmt |
| 2025-12-01 | Added UI-PROOF-VEX-0215-010 to address PVX1PVX10 proof-linked VEX UI gaps from `31-Nov-2025 FINDINGS.md`; status TODO pending API scope/caching/integrity rules and fixtures. | Project Mgmt |
| 2025-12-01 | Added TTE-GAPS-0215-011 to cover TTE1TTE10 Time-to-Evidence metric gaps from `31-Nov-2025 FINDINGS.md`; status TODO pending schema publication, SLO policy, and telemetry alignment. | Project Mgmt |
| 2025-12-06 | Corrected working directory to `src/Web/StellaOps.Web`; unblocked UI delivery tracker rows; fixtures still required. | Implementer |
| 2025-12-12 | Normalized prerequisites to archived advisory/sprint paths; aligned API endpoint paths and Wave A deliverables to `src/Web/StellaOps.Web`. | Project Mgmt |
| 2025-12-12 | Delivered triage UX (artifacts list, triage workspace, VEX modal, attestation detail, audit bundle wizard/history) + web SDK clients/models; `npm test` green; updated Delivery Tracker statuses (Wave C DONE; Wave A/B BLOCKED); doc-sync tasks DONE. | Implementer |
---
*Sprint created: 2025-11-28*

View File

@@ -1,10 +1,10 @@
# Sprint 0401 - Reachability Evidence Chain
# Sprint 0401.0001.0001 - Reachability Evidence Chain
## Topic & Scope
- Window: 2025-11-11 -> 2025-11-22 (UTC); finish the provable reachability pipeline so Sprint 0402 can focus on polish.
- Deliver function-level evidence chain (graph CAS -> replay -> DSSE -> policy/UI) with signed artifacts and replayable fixtures.
- Ship operator-facing docs/runbooks plus benchmarks that validate deterministic reachability scoring.
- **Working directory:** docs/implplan (cross-guild coordination; implementation happens in module paths noted per task).
- **Working directory:** `docs/implplan` (cross-guild coordination; implementation happens in module paths noted per task).
## Dependencies & Concurrency
- Upstream: Sprint 0400 foundation plus Sprint 0140 Runtime & Signals, Sprint 0185 Replay Core, Sprint 0186 Scanner Record Mode, Sprint 0187 Evidence Locker & CLI Integration.
@@ -127,10 +127,10 @@
## Action Tracker
| # | Action | Owner | Due (UTC) | Status | Notes |
| --- | --- | --- | --- | --- | --- |
| 1 | Capture checkpoint dates after Sprint 0400 closure signal. | Planning | 2025-12-15 | Open | Waiting on Sprint 0400 readiness update. |
| 2 | Confirm CAS hash alignment (BLAKE3 + sha256 addressing) across Scanner/Replay/Signals. | Platform Guild | 2025-12-10 | Done (2025-12-10) | CONTRACT-RICHGRAPH-V1-015 adopted; BLAKE3 graph_hash live in Scanner/Replay per GRAPH-CAS-401-001. |
| 3 | Schedule richgraph-v1 schema/hash alignment and rebaseline sprint dates. | Planning - Platform Guild | 2025-12-15 | Open (slipped) | Rebaseline sprint dates after 2025-12-10 alignment; align with new checkpoints on 2025-12-15/18. |
| 4 | Signals ingestion/probe readiness checkpoint for tasks 8-10, 17-18. | Signals Guild - Planning | 2025-12-18 | Open | Assess runtime ingestion/probe readiness and flip task statuses to DOING/BLOCKED accordingly. |
| 1 | Capture checkpoint dates after Sprint 0400 closure signal. | Planning | 2025-12-15 | TODO | Waiting on Sprint 0400 readiness update. |
| 2 | Confirm CAS hash alignment (BLAKE3 + sha256 addressing) across Scanner/Replay/Signals. | Platform Guild | 2025-12-10 | DONE (2025-12-10) | CONTRACT-RICHGRAPH-V1-015 adopted; BLAKE3 graph_hash live in Scanner/Replay per GRAPH-CAS-401-001. |
| 3 | Schedule richgraph-v1 schema/hash alignment and rebaseline sprint dates. | Planning - Platform Guild | 2025-12-15 | TODO (slipped) | Rebaseline sprint dates after 2025-12-10 alignment; align with new checkpoints on 2025-12-15/18. |
| 4 | Signals ingestion/probe readiness checkpoint for tasks 8-10, 17-18. | Signals Guild - Planning | 2025-12-18 | TODO | Assess runtime ingestion/probe readiness and flip task statuses to DOING/BLOCKED accordingly. |
## Decisions & Risks
- File renamed to `SPRINT_0401_0001_0001_reachability_evidence_chain.md` and normalized to template on 2025-11-22; scope unchanged.
@@ -154,6 +154,7 @@
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-13 | Marked SCANNER-NATIVE-401-015, GAP-REP-004, SCANNER-BUILDID-401-035, SCANNER-INITROOT-401-036, and GRAPH-HYBRID-401-053 as BLOCKED pending contracts on native lifters/toolchains, replay manifest v2 acceptance vectors/CAS gates, cross-RID build-id/code_id propagation, init synthetic-root schema/oracles, and graph-level DSSE/Rekor budget + golden fixtures. | Planning |
| 2025-12-12 | Normalized sprint header/metadata formatting and aligned Action Tracker status labels to `TODO`/`DONE`; no semantic changes. | Project Mgmt |
| 2025-12-12 | Rebaselined reachability wave: marked tasks 6/8/13-18/20-21/23/25-26/39-41/46-47/52/54-56/60 as BLOCKED pending upstream deps; set Wave 0401 status to DOING post richgraph alignment so downstream work can queue cleanly. | Planning |
| 2025-12-12 | RecordModeService bumped to replay manifest v2 (hashAlg fields, BLAKE3 graph hashes) and ReachabilityReplayWriter now emits hashAlg for graphs/traces; added synthetic runtime probe endpoint to Signals with deterministic builder + tests. | Implementer |
| 2025-12-12 | Unblocked runtime probes/scoring/replay: added synthetic runtime probe endpoint + builder in Signals, enabled scoring with synthetic feeds, and shipped ReachabilityReplayWriter manifest v2 with deterministic ordering/tests. Tasks 9/10/11 marked DONE. | Planning |

View File

@@ -0,0 +1,50 @@
# Sprint 0409.0001.0001 · Scanner Non-Language Scanners Quality
## Topic & Scope
- Improve OS/non-language analyzers for correctness, determinism, and evidence quality (paths, layer attribution, warnings).
- Add safe caching for OS package analyzers (surface cache + deterministic rootfs fingerprint) to reduce repeated scan time.
- Reduce avoidable CPU/IO cost (digest strategy, rpmdb sqlite query shape) without regressing evidence-chain value.
- **Working directory:** `src/Scanner`.
## Dependencies & Concurrency
- Reuses surface environment + cache (`ISurfaceCache`) already required by language analyzer caching.
- Expected to be independent from language analyzer work; safe to land in parallel.
## Documentation Prerequisites
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/scanner/architecture.md`
- `src/Scanner/AGENTS.md`
## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 1 | SCAN-NL-0409-001 | DONE | — | Scanner · Backend | Implement `OsRootfsFingerprint` (cheap + deterministic) and `OsAnalyzerSurfaceCache` (safe serializer) for `OSPackageAnalyzerResult` cache entries. |
| 2 | SCAN-NL-0409-002 | DONE | — | Scanner · Backend/QA | Wire OS analyzer caching into `CompositeScanAnalyzerDispatcher` (hit/miss metrics + fallbacks) and add worker tests proving cache reuse across jobs. |
| 3 | SCAN-NL-0409-003 | DONE | — | Scanner · Backend | Plumb analyzer warnings end-to-end: refactor `OsPackageAnalyzerBase` to support structured warnings and update OS analyzers to emit warnings deterministically (capped + coded). |
| 4 | SCAN-NL-0409-004 | DONE | — | Scanner · Backend/QA | Fix file-evidence correctness for non-Linux OS analyzers (rootfs-relative paths + `layerDigest` attribution via `OsFileEvidenceFactory`): `Pkgutil`, `Homebrew`, `MacOsBundle`, `Chocolatey`, `WinSxS`, `MSI`. Update tests accordingly. |
| 5 | SCAN-NL-0409-005 | DONE | — | Scanner · Backend/QA | Reduce avoidable hashing: adjust `OsFileEvidenceFactory` to avoid computing sha256 when other digests exist; improve `OsComponentMapper` primary digest selection (prefer strongest available). Add regression tests. |
| 6 | SCAN-NL-0409-006 | DONE | — | Scanner · Backend | RPM sqlite read path: avoid `SELECT *` and column-scanning where feasible (schema probe + targeted column selection). Add unit coverage for schema variants. |
| 7 | SCAN-NL-0409-007 | DONE | — | Scanner · Backend/QA | Native “unknowns” quality: emit unknowns even when dependency list is empty; extract ELF `.dynsym` undefined symbols for unknown edges; add regression test. |
| 8 | SCAN-NL-0409-008 | DONE | — | Scanner · Docs | Document OS analyzer evidence semantics (paths/digests/warnings) and caching behavior under `docs/modules/scanner/` (and link from sprint Decisions & Risks). |
## Execution Log
| Date (UTC) | Update | Owner |
| --- | --- | --- |
| 2025-12-12 | Sprint created; backlog drafted. | Planning |
| 2025-12-12 | Implemented OS analyzer fingerprint + surface cache adapter. | Scanner |
| 2025-12-12 | Wired OS cache into worker dispatcher; added worker cache hit/miss metrics; fixed worker compilation and updated worker tests. | Scanner |
| 2025-12-12 | Completed warnings plumbing + evidence-path fixes + digest strategy updates; analyzer tests passing. | Scanner |
| 2025-12-12 | Optimized rpmdb sqlite reader (schema probe + targeted selection/query); added tests. | Scanner |
| 2025-12-12 | Improved native “unknowns” (ELF `.dynsym` undefined symbols) and added regression test. | Scanner |
| 2025-12-12 | Documented OS/non-language evidence contract and caching behavior. | Scanner |
## Decisions & Risks
- **OS cache safety:** Only cache when the rootfs fingerprint is representative of analyzer inputs; otherwise bypass cache to avoid stale results.
- **Evidence path semantics:** OS file evidence paths are rootfs-relative and stable; analyzers must not emit host paths or per-analyzer relative paths.
- **Digest strategy:** Avoid unbounded hashing; prefer using package-manager-provided digests (even if weaker than sha256) and only hash content when justified.
- **Evidence contract:** `docs/modules/scanner/os-analyzers-evidence.md`.
## Next Checkpoints
- 2025-12-12: Sprint completed; all tasks set to DONE.

View File

@@ -112,11 +112,11 @@ Scanner.Storage now runs on PostgreSQL with migrations and DI wiring; MongoDB im
### T10.11: Package and Project Cleanup
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- |
| 40 | MR-T10.11.1 | BLOCKED | Scanner.Storage still depends on MongoDB.Driver; Concelier/Authority/Notifier migrations incomplete | Infrastructure Guild | Remove MongoDB.Driver package references from all csproj files |
| 41 | MR-T10.11.2 | BLOCKED | MR-T10.11.1 | Infrastructure Guild | Remove MongoDB.Bson package references from all csproj files |
| 40 | MR-T10.11.1 | DONE (2025-12-12) | All MongoDB.Driver package references removed | Infrastructure Guild | Remove MongoDB.Driver package references from all csproj files |
| 41 | MR-T10.11.2 | DONE (2025-12-12) | All MongoDB.Bson package references removed | Infrastructure Guild | Remove MongoDB.Bson package references from all csproj files |
| 42 | MR-T10.11.3 | DONE | MR-T10.11.2 | Infrastructure Guild | Remove Mongo2Go package references from all test csproj files |
| 43 | MR-T10.11.4 | BLOCKED | MR-T10.11.3 | Infrastructure Guild | Remove `StellaOps.Provenance.Mongo` project |
| 44 | MR-T10.11.5 | BLOCKED | MR-T10.11.4 | Infrastructure Guild | Final grep verification: zero MongoDB references |
| 43 | MR-T10.11.4 | DONE (2025-12-12) | Renamed to StellaOps.Provenance; all refs updated | Infrastructure Guild | Rename `StellaOps.Provenance.Mongo` project (cosmetic - no package deps) |
| 44 | MR-T10.11.5 | DONE (2025-12-12) | Verified zero MongoDB package refs in csproj; shims kept for compat | Infrastructure Guild | Final grep verification: zero MongoDB references |
## Wave Coordination
- Single-wave execution with module-by-module sequencing to keep the build green after each subtask.
@@ -257,3 +257,13 @@ Scanner.Storage now runs on PostgreSQL with migrations and DI wiring; MongoDB im
| 2025-12-11 | T10.11.3 in progress: Signals.Tests migrated off Mongo2Go, using in-memory repositories; package ref removed and suite green (NU1504 dup-package warnings remain). | Signals Guild |
| 2025-12-11 | Completed MR-T10.10.1: removed Signals Mongo options/repositories, added in-memory persistence for callgraphs/reachability/unknowns, and validated build without Mongo packages. | Signals Guild |
| 2025-12-11 | MR-T10.11.4 blocked: `StellaOps.Provenance.Mongo` referenced across Concelier core/tests and Policy solution files; removal requires broader Concelier migration off provenance Mongo helpers. | Infrastructure Guild |
| 2025-12-12 | Removed MongoDB.Bson package from Replay.Core; created local BsonCompat.cs shim attributes (BsonIdAttribute, BsonIgnoreExtraElementsAttribute). | Infrastructure Guild |
| 2025-12-12 | Removed Mongo2Go package and MongoBackedCreateSimulationPersists test from Scheduler.WebService.Tests; tests now use in-memory shims only. | Scheduler Guild |
| 2025-12-12 | Deleted Concelier.Storage.Postgres.Tests MongoDB parity test files (MongoFixture.cs, GhsaImporterMongoTests.cs, NvdImporterMongoTests.cs, OsvImporterMongoTests.cs, DualImportParityTests.cs, ParityRunnerTests.cs, NvdImporterTests.cs) and entire Parity/ subfolder. | Concelier Guild |
| 2025-12-12 | Deleted tests/Concelier/StellaOps.Concelier.Storage.Mongo.Tests project folder entirely. | Concelier Guild |
| 2025-12-12 | Deleted offline/packages MongoDB packages (mongodb.bson, mongodb.driver, mongodb.driver.core, mongodb.libmongocrypt, mongo2go). | Infrastructure Guild |
| 2025-12-12 | **Package cleanup verification:** Zero MongoDB.Driver/MongoDB.Bson/Mongo2Go PackageReference Include entries remain in csproj files. Only defensive `<PackageReference Remove="Mongo2Go">` entries exist in some test projects. In-memory shims (Concelier MongoCompat, Scheduler MongoStubs, Authority.Storage.Mongo) kept for code compatibility; they contain no external dependencies. | Infrastructure Guild |
| 2025-12-12 | **Provenance.Mongo investigation:** `StellaOps.Provenance.Mongo` has no MongoDB package dependencies - only references Concelier.Models. Contains BSON-like type stubs (BsonDocument, BsonArray, etc.) and provenance helpers. Used by 13 files in Concelier Core/Tests. Renamed task MR-T10.11.4 to DEFERRED - cosmetic rename only, not blocking MongoDB removal. | Infrastructure Guild |
| 2025-12-12 | **Completed MR-T10.11.4:** Renamed `StellaOps.Provenance.Mongo``StellaOps.Provenance`, updated namespace from `StellaOps.Provenance.Mongo``StellaOps.Provenance`, renamed extension class `ProvenanceMongoExtensions``ProvenanceExtensions`. Renamed test project `StellaOps.Events.Mongo.Tests``StellaOps.Events.Provenance.Tests`. Updated 13 files with using statements. All builds and tests pass. | Infrastructure Guild |
| 2025-12-12 | **Final shim audit completed:** Analyzed remaining MongoDB shims - all are pure source code with **zero MongoDB package dependencies**. (1) `Concelier.Models/MongoCompat/DriverStubs.cs` (354 lines): full MongoDB.Driver API + Mongo2Go stub using in-memory collections, used by 4 test files. (2) `Scheduler.Models/MongoStubs.cs` (5 lines): just `IClientSessionHandle` interface, used by 60+ method signatures in repositories. (3) `Authority.Storage.Mongo` (10 files): full shim project, only depends on DI Abstractions. All shims use `namespace MongoDB.Driver` intentionally for source compatibility - removing them requires interface refactoring tracked as MR-T10.1.4 (BLOCKED on test fixture migration). **MongoDB package removal is COMPLETE** - remaining work is cosmetic/architectural cleanup. | Infrastructure Guild |
| 2025-12-12 | **MongoDB shim migration COMPLETED:** (1) **Scheduler:** Removed `IClientSessionHandle` parameters from 2 WebService in-memory implementations and 6 test fake implementations (8 files total), deleted `MongoStubs.cs`. (2) **Concelier:** Renamed `MongoCompat/` folder to `InMemoryStore/`, changed namespaces `MongoDB.Driver``StellaOps.Concelier.InMemoryDriver`, `Mongo2Go``StellaOps.Concelier.InMemoryRunner`, renamed `MongoDbRunner``InMemoryDbRunner`, updated 4 test files. (3) **Authority:** Renamed project `Storage.Mongo``Storage.InMemory`, renamed namespace `MongoDB.Driver``StellaOps.Authority.InMemoryDriver`, updated 47 C# files and 3 csproj references. (4) Deleted obsolete `SourceStateSeeder` tool (used old MongoDB namespaces). **Zero `using MongoDB.Driver;` or `using Mongo2Go;` statements remain in codebase.** | Infrastructure Guild |

View File

@@ -5,10 +5,11 @@
Each card below pairs the headline capability with the evidence that backs it and why it matters day to day.
<!-- TODO: Review for separate approval - added Decision Capsules as feature 0 -->
## 0. Decision Capsules Audit-Grade Evidence Bundles (2025-12)
- **What it is:** Every scan result is sealed in a **Decision Capsule**a content-addressed bundle containing all inputs, outputs, and evidence needed to reproduce and verify the vulnerability decision.
## 0. Decision Capsules - Audit-Grade Evidence Bundles (2025-12)
- **What it is:** Every scan result is sealed in a **Decision Capsule**-a content-addressed bundle containing all inputs, outputs, and evidence needed to reproduce and verify the vulnerability decision.
- **Evidence:** Each capsule includes: exact SBOM (and source provenance if available), exact vuln feed snapshots (or IDs to frozen snapshots), reachability evidence (static artifacts + runtime traces if any), policy version + lattice rules, derived VEX statements, and signatures over all of the above.
- **Why it matters:** Auditors can re-run any capsule bit-for-bit to verify the outcome. This is the heart of audit-grade assurance—every decision becomes a provable, replayable fact.
- **UX surface:** Vulnerability triage is built around VEX-first decisions and one-click immutable audit bundles; reference `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`.
- **Why it matters:** Auditors can re-run any capsule bit-for-bit to verify the outcome. This is the heart of audit-grade assurance-every decision becomes a provable, replayable fact.
## 1. Delta SBOM Engine
- **What it is:** Layer-aware ingestion keeps the SBOM catalog content-addressed; rescans only fetch new layers and update dependency/vulnerability cartographs.

View File

@@ -78,6 +78,18 @@ All endpoints require Authority-issued JWT + DPoP tokens with scopes `export:run
| `export_distributions` | Distribution artefacts. | `run_id`, `type` (`http`, `oci`, `object`), `location`, `sha256`, `size_bytes`, `expires_at`. | `expires_at` used for retention policies and automatic pruning. |
| `export_events` | Timeline of state transitions and metrics. | `run_id`, `event_type`, `message`, `at`, `metrics`. | Feeds SSE stream and audit trails. |
## Audit bundles (immutable triage exports)
Audit bundles are a specialized Export Center output: a deterministic, immutable evidence pack for a single subject (and optional time window) suitable for audits and incident response.
- **Schema**: `docs/schemas/audit-bundle-index.schema.json` (bundle index/manifest with integrity hashes and referenced artefacts).
- **Core APIs**:
- `POST /v1/audit-bundles` - Create a new bundle (async generation).
- `GET /v1/audit-bundles` - List previously created bundles.
- `GET /v1/audit-bundles/{bundleId}` - Returns job metadata (`Accept: application/json`) or streams bundle bytes (`Accept: application/octet-stream`).
- **Typical contents**: vuln reports, SBOM(s), VEX decisions, policy evaluations, and DSSE attestations, plus an integrity root hash and optional OCI reference.
- **Reference**: `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`.
## Adapter responsibilities
- **JSON (`json:raw`, `json:policy`).**
- Ensures canonical casing, timezone normalization, and linkset preservation.

View File

@@ -2,13 +2,14 @@
Scanner analyses container images layer-by-layer, producing deterministic SBOM fragments, diffs, and signed reports.
## Latest updates (2025-12-03)
## Latest updates (2025-12-12)
- Deterministic SBOM composition fixture published at `docs/modules/scanner/fixtures/deterministic-compose/` with DSSE, `_composition.json`, BOM, and hashes; doc `deterministic-sbom-compose.md` promoted to Ready v1.0 with offline verification steps.
- Node analyzer now ingests npm/yarn/pnpm lockfiles, emitting `DeclaredOnly` components with lock provenance. The CLI companion command `stella node lock-validate` runs the collector offline, surfaces declared-only or missing-lock packages, and emits telemetry via `stellaops.cli.node.lock_validate.count`.
- Python analyzer picks up `requirements*.txt`, `Pipfile.lock`, and `poetry.lock`, tagging installed distributions with lock provenance and generating declared-only components for policy. Use `stella python lock-validate` to run the same checks locally before images are built.
- Java analyzer now parses `gradle.lockfile`, `gradle/dependency-locks/**/*.lockfile`, and `pom.xml` dependencies via the new `JavaLockFileCollector`, merging lock metadata onto jar evidence and emitting declared-only components when jars are absent. The new CLI verb `stella java lock-validate` reuses that collector offline (table/JSON output) and records `stellaops.cli.java.lock_validate.count{outcome}` for observability.
- Worker/WebService now resolve cache roots and feature flags via `StellaOps.Scanner.Surface.Env`; misconfiguration warnings are documented in `docs/modules/scanner/design/surface-env.md` and surfaced through startup validation.
- Platform events rollout (2025-10-19) continues to publish scanner.report.ready@1 and scanner.scan.completed@1 envelopes with embedded DSSE payloads (see docs/updates/2025-10-19-scanner-policy.md and docs/updates/2025-10-19-platform-events.md). Service and consumer tests should round-trip the canonical samples under docs/events/samples/.
- OS/non-language analyzers: evidence is rootfs-relative, warnings are structured/capped, hashing is bounded, and Linux OS analyzers support surface-cache reuse. See `os-analyzers-evidence.md`.
## Responsibilities
- Expose APIs (WebService) for scan orchestration, diffing, and artifact retrieval.
@@ -38,6 +39,7 @@ Scanner analyses container images layer-by-layer, producing deterministic SBOM f
- ./operations/entrypoint.md
- ./operations/secret-leak-detection.md
- ./operations/dsse-rekor-operator-guide.md
- ./os-analyzers-evidence.md
- ./design/macos-analyzer.md
- ./design/windows-analyzer.md
- ../benchmarks/scanner/deep-dives/macos.md

View File

@@ -0,0 +1,74 @@
# OS Analyzer Evidence Semantics (Non-Language Scanners)
This document defines the **evidence contract** produced by OS/non-language analyzers (apk/dpkg/rpm + Windows/macOS OS analyzers) so downstream SBOM/attestation logic can rely on stable, deterministic semantics.
## Evidence Paths
- `OSPackageFileEvidence.Path` is **rootfs-relative** and **normalized**:
- No leading slash (`/`).
- Forward slashes only (`/`), even on Windows inputs.
- Never a host path.
- Any analyzer-specific absolute path must be converted to rootfs-relative before emission.
- Helper: `StellaOps.Scanner.Analyzers.OS.Helpers.OsPath.TryGetRootfsRelative(...)`.
Examples:
- Good: `usr/bin/bash`
- Bad: `/usr/bin/bash`
- Bad: `C:\scans\rootfs\usr\bin\bash`
## Layer Attribution
- `OSPackageFileEvidence.LayerDigest` is **best-effort** attribution derived from scan metadata:
- `ScanMetadataKeys.LayerDirectories` (optional mapping of layer digest → extracted directory)
- `ScanMetadataKeys.CurrentLayerDigest` (fallback/default)
- Helper: `StellaOps.Scanner.Analyzers.OS.Helpers.OsFileEvidenceFactory`.
## Digest & Hashing Strategy
Default posture is **avoid unbounded hashing**:
- Prefer package-manager-provided digests when present (`OSPackageFileEvidence.Digests` / `OSPackageFileEvidence.Sha256`).
- Compute `sha256` only when:
- No digests are present, and
- File exists, and
- File size is ≤ 16 MiB (`OsFileEvidenceFactory` safeguard).
- Primary digest selection for file evidence metadata prefers strongest available:
- `sha512``sha384``sha256``sha1``md5`
## Analyzer Warnings
OS analyzers may emit `AnalyzerWarning` entries (`Code`, `Message`) for partial/edge conditions (missing db, parse errors, unexpected layout).
Normalization rules (in `OsPackageAnalyzerBase`):
- Deduplicate by `(Code, Message)`.
- Stable sort by `Code` then `Message` (ordinal).
- Cap at 50 warnings.
## OS Analyzer Caching (Surface Cache)
Linux OS analyzers (apk/dpkg/rpm) support **safe, deterministic reuse** via `ISurfaceCache`:
- Cache key: `(tenant, analyzerId, rootfsFingerprint)` under namespace `scanner/os/analyzers`.
- Fingerprint inputs are intentionally narrow: a single **analyzer-specific** “DB fingerprint file”:
- `apk`: `lib/apk/db/installed`
- `dpkg`: `var/lib/dpkg/status`
- `rpm`: `var/lib/rpm/rpmdb.sqlite` (preferred) or legacy `Packages` fallback
- Fingerprint payload includes:
- Root path + analyzerId
- Relative fingerprint file path
- File length + `LastWriteTimeUtc` (ms)
- Optional file-content sha256 when the file is ≤ 8 MiB
Worker wiring:
- `StellaOps.Scanner.Worker.Processing.CompositeScanAnalyzerDispatcher` records cache hit/miss counters per analyzer.
## RPM sqlite Reader Notes
When `rpmdb.sqlite` is present, the reader avoids `SELECT *` and column scanning:
- Uses `PRAGMA table_info(Packages)` to select a likely RPM header blob column (prefers `hdr`/`header`, excludes `pkgId` when possible).
- Queries only `pkgKey` + header blob column for parsing.

View File

@@ -45,6 +45,7 @@
├─ runtime/ # Zastava posture, drift events, admission decisions
├─ policy/ # rules editor (YAML/Rego), exemptions, previews
├─ vex/ # VEX explorer (claims, consensus, conflicts)
├─ triage/ # vulnerability triage (artifact-first), VEX decisions, audit bundles
├─ concelier/ # source health, export cursors, rebuild/export triggers
├─ attest/ # attestation proofs, verification bundles, Rekor links
├─ admin/ # tenants, roles, clients, quotas, licensing posture
@@ -113,6 +114,15 @@ Each feature folder builds as a **standalone route** (lazy loaded). All HTTP sha
* **Quotas**: per license plan, counters, throttle events.
* **Licensing posture**: last PoE introspection snapshot (redacted), release window.
### 3.9 Vulnerability triage (VEX-first)
* **Routes**: `/triage/artifacts`, `/triage/artifacts/:artifactId`, `/triage/audit-bundles`, `/triage/audit-bundles/new`.
* **Workspace**: artifact-first split layout (finding cards on the left; explainability tabs on the right: Overview, Reachability, Policy, Attestations).
* **VEX decisions**: evidence-first VEX modal with scope + validity + evidence links; bulk apply supported; uses `/v1/vex-decisions`.
* **Audit bundles**: "Create immutable audit bundle" UX to build and download an evidence pack; uses `/v1/audit-bundles`.
* **Schemas**: `docs/schemas/vex-decision.schema.json`, `docs/schemas/attestation-vuln-scan.schema.json`, `docs/schemas/audit-bundle-index.schema.json`.
* **Reference**: `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`.
---
## 4) Auth, sessions & RBAC

View File

@@ -79,7 +79,7 @@ CLI mirrors these endpoints (`stella findings list|view|update|export`). Console
## 8) VEX-First Triage UX
> Reference: Product advisory `28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`
> Reference: Product advisory `docs/product-advisories/archived/27-Nov-2025-superseded/28-Nov-2025 - Vulnerability Triage UX & VEX-First Decisioning.md`
### 8.1 Evidence-First Finding Cards
@@ -175,6 +175,8 @@ Immutable audit bundles follow the `AuditBundleIndex` schema (`docs/schemas/audi
- `GET /v1/audit-bundles/{bundleId}` - Download bundle (ZIP or OCI)
- `GET /v1/audit-bundles` - List previously created bundles
`GET /v1/audit-bundles/{bundleId}` may use content negotiation: `Accept: application/json` returns job metadata; `Accept: application/octet-stream` streams bundle bytes.
### 8.6 Industry Pattern Alignment
The triage UX aligns with industry patterns from:

View File

@@ -9,9 +9,9 @@ using StellaOps.Authority.Plugin.Ldap.Connections;
using StellaOps.Authority.Plugin.Ldap.Tests.Fakes;
using StellaOps.Authority.Plugin.Ldap.Tests.TestHelpers;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Auth.Abstractions;
using Xunit;

View File

@@ -10,9 +10,9 @@ using StellaOps.Authority.Plugin.Ldap.Monitoring;
using StellaOps.Authority.Plugin.Ldap.Tests.TestHelpers;
using StellaOps.Authority.Plugin.Ldap.Tests.Fakes;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using Xunit;
namespace StellaOps.Authority.Plugin.Ldap.Tests.Credentials;

View File

@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Plugin.Ldap.Tests.TestHelpers;

View File

@@ -5,12 +5,12 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugin.Ldap.Connections;
using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Auth.Abstractions;
namespace StellaOps.Authority.Plugin.Ldap.ClientProvisioning;

View File

@@ -11,8 +11,8 @@ using StellaOps.Authority.Plugin.Ldap.ClientProvisioning;
using StellaOps.Authority.Plugin.Ldap.Connections;
using StellaOps.Authority.Plugin.Ldap.Monitoring;
using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Plugin.Ldap.Credentials;

View File

@@ -9,7 +9,7 @@ using StellaOps.Authority.Plugin.Ldap.Connections;
using StellaOps.Authority.Plugin.Ldap.Credentials;
using StellaOps.Authority.Plugin.Ldap.Monitoring;
using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Plugin.Ldap;

View File

@@ -18,7 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\\StellaOps.Authority.Plugins.Abstractions\\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="..\\StellaOps.Auth.Abstractions\\StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="..\\StellaOps.Authority.Storage.Mongo\\StellaOps.Authority.Storage.Mongo.csproj" />
<ProjectReference Include="..\\StellaOps.Authority.Storage.InMemory\\StellaOps.Authority.Storage.InMemory.csproj" />
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Plugin\\StellaOps.Plugin.csproj" />
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Authority.Storage.Postgres\\StellaOps.Authority.Storage.Postgres.csproj" />
</ItemGroup>

View File

@@ -3,11 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using Xunit;
namespace StellaOps.Authority.Plugin.Standard.Tests;

View File

@@ -8,13 +8,13 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard;
using StellaOps.Authority.Plugin.Standard.Bootstrap;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
@@ -24,7 +24,7 @@ public class StandardPluginRegistrarTests
[Fact]
public async Task Register_ConfiguresIdentityProviderAndSeedsBootstrapUser()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-tests");
var configuration = new ConfigurationBuilder()
@@ -86,7 +86,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_LogsWarning_WhenPasswordPolicyWeaker()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-password-policy");
var configuration = new ConfigurationBuilder()
@@ -131,7 +131,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_ForcesPasswordCapability_WhenManifestMissing()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-capabilities");
var configuration = new ConfigurationBuilder().Build();
@@ -163,7 +163,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_Throws_WhenBootstrapConfigurationIncomplete()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-bootstrap-validation");
var configuration = new ConfigurationBuilder()
@@ -197,7 +197,7 @@ public class StandardPluginRegistrarTests
[Fact]
public void Register_NormalizesTokenSigningKeyDirectory()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
var database = client.GetDatabase("registrar-token-signing");
var configuration = new ConfigurationBuilder()
@@ -389,7 +389,7 @@ internal sealed class TestAuthEventSink : IAuthEventSink
internal static class StandardPluginRegistrarTestHelpers
{
public static ServiceCollection CreateServiceCollection(
IMongoDatabase database,
IDatabase database,
IAuthEventSink? authEventSink = null,
IAuthorityCredentialAuditContextAccessor? auditContextAccessor = null)
{

View File

@@ -5,7 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Driver;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;
@@ -16,14 +16,14 @@ namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardUserCredentialStoreTests : IAsyncLifetime
{
private readonly IMongoDatabase database;
private readonly IDatabase database;
private readonly StandardPluginOptions options;
private readonly StandardUserCredentialStore store;
private readonly TestAuditLogger auditLogger;
public StandardUserCredentialStoreTests()
{
var client = new InMemoryMongoClient();
var client = new InMemoryClient();
database = client.GetDatabase("authority-tests");
options = new StandardPluginOptions
{
@@ -171,9 +171,9 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.True(auditEntry.Success);
Assert.Equal("legacy", auditEntry.Username);
var updated = await database.GetCollection<StandardUserDocument>("authority_users_standard")
.Find(u => u.NormalizedUsername == "legacy")
.FirstOrDefaultAsync();
var results = await database.GetCollection<StandardUserDocument>("authority_users_standard")
.FindAsync(u => u.NormalizedUsername == "legacy");
var updated = results.FirstOrDefault();
Assert.NotNull(updated);
Assert.StartsWith("$argon2id$", updated!.PasswordHash, StringComparison.Ordinal);

View File

@@ -7,7 +7,7 @@ using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Bootstrap;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Repositories;
using StellaOps.Cryptography;
using StellaOps.Cryptography.DependencyInjection;

View File

@@ -16,7 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Storage.Mongo\StellaOps.Authority.Storage.Mongo.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Storage.InMemory\StellaOps.Authority.Storage.InMemory.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Authority.Storage.Postgres/StellaOps.Authority.Storage.Postgres.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Plugin.Standard.Storage;

View File

@@ -1,14 +1,14 @@
using System.Linq.Expressions;
namespace MongoDB.Driver;
namespace StellaOps.Authority.InMemoryDriver;
/// <summary>
/// Compatibility shim for MongoDB IMongoCollection interface.
/// In PostgreSQL mode, this provides an in-memory implementation.
/// Compatibility shim for collection interface.
/// Provides an in-memory implementation.
/// </summary>
public interface IMongoCollection<TDocument>
public interface ICollection<TDocument>
{
IMongoDatabase Database { get; }
IDatabase Database { get; }
string CollectionNamespace { get; }
Task<TDocument?> FindOneAsync(Expression<Func<TDocument, bool>> filter, CancellationToken cancellationToken = default);
@@ -20,38 +20,38 @@ public interface IMongoCollection<TDocument>
}
/// <summary>
/// Compatibility shim for MongoDB IMongoDatabase interface.
/// Compatibility shim for database interface.
/// </summary>
public interface IMongoDatabase
public interface IDatabase
{
string DatabaseNamespace { get; }
IMongoCollection<TDocument> GetCollection<TDocument>(string name);
ICollection<TDocument> GetCollection<TDocument>(string name);
}
/// <summary>
/// Compatibility shim for MongoDB IMongoClient interface.
/// Compatibility shim for client interface.
/// </summary>
public interface IMongoClient
public interface IClient
{
IMongoDatabase GetDatabase(string name);
IDatabase GetDatabase(string name);
}
/// <summary>
/// In-memory implementation of IMongoCollection for compatibility.
/// In-memory implementation of ICollection for compatibility.
/// </summary>
public class InMemoryMongoCollection<TDocument> : IMongoCollection<TDocument>
public class InMemoryCollection<TDocument> : ICollection<TDocument>
{
private readonly List<TDocument> _documents = new();
private readonly IMongoDatabase _database;
private readonly IDatabase _database;
private readonly string _name;
public InMemoryMongoCollection(IMongoDatabase database, string name)
public InMemoryCollection(IDatabase database, string name)
{
_database = database;
_name = name;
}
public IMongoDatabase Database => _database;
public IDatabase Database => _database;
public string CollectionNamespace => _name;
public Task<TDocument?> FindOneAsync(Expression<Func<TDocument, bool>> filter, CancellationToken cancellationToken = default)
@@ -109,43 +109,43 @@ public class InMemoryMongoCollection<TDocument> : IMongoCollection<TDocument>
}
/// <summary>
/// In-memory implementation of IMongoDatabase for compatibility.
/// In-memory implementation of IDatabase for compatibility.
/// </summary>
public class InMemoryMongoDatabase : IMongoDatabase
public class InMemoryDatabase : IDatabase
{
private readonly Dictionary<string, object> _collections = new();
private readonly string _name;
public InMemoryMongoDatabase(string name)
public InMemoryDatabase(string name)
{
_name = name;
}
public string DatabaseNamespace => _name;
public IMongoCollection<TDocument> GetCollection<TDocument>(string name)
public ICollection<TDocument> GetCollection<TDocument>(string name)
{
if (!_collections.TryGetValue(name, out var collection))
{
collection = new InMemoryMongoCollection<TDocument>(this, name);
collection = new InMemoryCollection<TDocument>(this, name);
_collections[name] = collection;
}
return (IMongoCollection<TDocument>)collection;
return (ICollection<TDocument>)collection;
}
}
/// <summary>
/// In-memory implementation of IMongoClient for compatibility.
/// In-memory implementation of IClient for compatibility.
/// </summary>
public class InMemoryMongoClient : IMongoClient
public class InMemoryClient : IClient
{
private readonly Dictionary<string, IMongoDatabase> _databases = new();
private readonly Dictionary<string, IDatabase> _databases = new();
public IMongoDatabase GetDatabase(string name)
public IDatabase GetDatabase(string name)
{
if (!_databases.TryGetValue(name, out var database))
{
database = new InMemoryMongoDatabase(name);
database = new InMemoryDatabase(name);
_databases[name] = database;
}
return database;

View File

@@ -1,15 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using StellaOps.Authority.Storage.Mongo.Initialization;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Storage.InMemory.Initialization;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Storage.Mongo.Extensions;
/// <summary>
/// Compatibility shim storage options. In PostgreSQL mode, these are largely unused.
/// </summary>
public sealed class AuthorityMongoStorageOptions
public sealed class AuthorityStorageOptions
{
public string ConnectionString { get; set; } = string.Empty;
public string DatabaseName { get; set; } = "authority";
@@ -28,9 +28,9 @@ public static class ServiceCollectionExtensions
/// </summary>
public static IServiceCollection AddAuthorityMongoStorage(
this IServiceCollection services,
Action<AuthorityMongoStorageOptions> configureOptions)
Action<AuthorityStorageOptions> configureOptions)
{
var options = new AuthorityMongoStorageOptions();
var options = new AuthorityStorageOptions();
configureOptions(options);
services.AddSingleton(options);
@@ -38,19 +38,19 @@ public static class ServiceCollectionExtensions
return services;
}
private static void RegisterMongoCompatServices(IServiceCollection services, AuthorityMongoStorageOptions options)
private static void RegisterMongoCompatServices(IServiceCollection services, AuthorityStorageOptions options)
{
// Register the initializer (no-op for Postgres mode)
services.AddSingleton<AuthorityMongoInitializer>();
services.AddSingleton<AuthorityStorageInitializer>();
// Register null session accessor
services.AddSingleton<IAuthorityMongoSessionAccessor, NullAuthorityMongoSessionAccessor>();
services.AddSingleton<IAuthoritySessionAccessor, NullAuthoritySessionAccessor>();
// Register in-memory MongoDB shims for compatibility
var inMemoryClient = new InMemoryMongoClient();
// Register in-memory shims for compatibility
var inMemoryClient = new InMemoryClient();
var inMemoryDatabase = inMemoryClient.GetDatabase(options.DatabaseName);
services.AddSingleton<IMongoClient>(inMemoryClient);
services.AddSingleton<IMongoDatabase>(inMemoryDatabase);
services.AddSingleton<IClient>(inMemoryClient);
services.AddSingleton<IDatabase>(inMemoryDatabase);
// Register in-memory store implementations
// These should be replaced by Postgres-backed implementations over time

View File

@@ -1,10 +1,10 @@
namespace StellaOps.Authority.Storage.Mongo.Initialization;
namespace StellaOps.Authority.Storage.InMemory.Initialization;
/// <summary>
/// Compatibility shim for MongoDB initializer. In PostgreSQL mode, this is a no-op.
/// Compatibility shim for storage initializer. In PostgreSQL mode, this is a no-op.
/// The actual initialization is handled by PostgreSQL migrations.
/// </summary>
public sealed class AuthorityMongoInitializer
public sealed class AuthorityStorageInitializer
{
/// <summary>
/// Initializes the database. In PostgreSQL mode, this is a no-op as migrations handle setup.

View File

@@ -8,9 +8,9 @@ public interface IClientSessionHandle : IDisposable
}
/// <summary>
/// Compatibility shim for MongoDB session accessor. In PostgreSQL mode, this returns null.
/// Compatibility shim for database session accessor. In PostgreSQL mode, this returns null.
/// </summary>
public interface IAuthorityMongoSessionAccessor
public interface IAuthoritySessionAccessor
{
IClientSessionHandle? CurrentSession { get; }
ValueTask<IClientSessionHandle?> GetSessionAsync(CancellationToken cancellationToken);
@@ -19,7 +19,7 @@ public interface IAuthorityMongoSessionAccessor
/// <summary>
/// In-memory implementation that always returns null session.
/// </summary>
public sealed class NullAuthorityMongoSessionAccessor : IAuthorityMongoSessionAccessor
public sealed class NullAuthoritySessionAccessor : IAuthoritySessionAccessor
{
public IClientSessionHandle? CurrentSession => null;

View File

@@ -1,7 +1,7 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
namespace StellaOps.Authority.Storage.Mongo.Stores;
namespace StellaOps.Authority.Storage.InMemory.Stores;
/// <summary>
/// Store interface for bootstrap invites.

View File

@@ -1,9 +1,9 @@
using System.Collections.Concurrent;
using System.Threading;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
namespace StellaOps.Authority.Storage.Mongo.Stores;
namespace StellaOps.Authority.Storage.InMemory.Stores;
/// <summary>
/// In-memory implementation of bootstrap invite store for development/testing.

View File

@@ -9,9 +9,9 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Tests.Infrastructure;
using StellaOps.Configuration;
using Xunit;

View File

@@ -13,9 +13,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Airgap;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Tests.Infrastructure;
using Xunit;
@@ -171,7 +171,7 @@ public sealed class AirgapAuditEndpointsTests : IClassFixture<AuthorityWebApplic
var store = new TestAirgapAuditStore();
_airgapStore = store;
services.Replace(ServiceDescriptor.Singleton<IAuthorityAirgapAuditStore>(store));
services.Replace(ServiceDescriptor.Singleton<IAuthorityMongoSessionAccessor, NullAuthorityMongoSessionAccessor>());
services.Replace(ServiceDescriptor.Singleton<IAuthoritySessionAccessor, NullAuthoritySessionAccessor>());
services.Replace(ServiceDescriptor.Singleton<TimeProvider>(timeProvider));
services.AddAuthentication(options =>
{

View File

@@ -1,10 +1,10 @@
using System.Linq;
using Microsoft.Extensions.Logging;
using StellaOps.Authority.Audit;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Sessions;
namespace StellaOps.Authority.Tests.Audit;

View File

@@ -6,9 +6,9 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Authority.Bootstrap;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Cryptography.Audit;
using Xunit;

View File

@@ -17,9 +17,9 @@ using StellaOps.Auth.Abstractions;
using Microsoft.AspNetCore.Routing;
using StellaOps.Configuration;
using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Tests.Infrastructure;
using StellaOps.Cryptography.Audit;
using Xunit;
@@ -302,7 +302,7 @@ public sealed class ServiceAccountAdminEndpointsTests : IClassFixture<AuthorityW
foreach (var tokenId in tokenIds)
{
var sessionAccessor = scope.ServiceProvider.GetRequiredService<IAuthorityMongoSessionAccessor>();
var sessionAccessor = scope.ServiceProvider.GetRequiredService<IAuthoritySessionAccessor>();
var session = await sessionAccessor.GetSessionAsync(CancellationToken.None);
var token = await tokenStore.FindByTokenIdAsync(tokenId, CancellationToken.None, session);
Assert.NotNull(token);

View File

@@ -9,9 +9,9 @@ using Microsoft.Extensions.Hosting;
using Xunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.Authority.Storage.Mongo.Extensions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Extensions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Postgres;
namespace StellaOps.Authority.Tests.Infrastructure;
@@ -105,7 +105,7 @@ public sealed class AuthorityWebApplicationFactory : WebApplicationFactory<Progr
services.RemoveAll<IAuthorityRefreshTokenStore>();
services.RemoveAll<IAuthorityAirgapAuditStore>();
services.RemoveAll<IAuthorityRevocationExportStateStore>();
services.RemoveAll<IAuthorityMongoSessionAccessor>();
services.RemoveAll<IAuthoritySessionAccessor>();
services.AddAuthorityMongoStorage(options =>
{

View File

@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Tests.Infrastructure;

View File

@@ -30,9 +30,9 @@ using StellaOps.Authority.Airgap;
using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.OpenIddict.Handlers;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.RateLimiting;
using StellaOps.Cryptography.Audit;
using Xunit;
@@ -4475,7 +4475,7 @@ internal sealed class StubCertificateValidator : IAuthorityClientCertificateVali
}
}
internal sealed class NullMongoSessionAccessor : IAuthorityMongoSessionAccessor
internal sealed class NullMongoSessionAccessor : IAuthoritySessionAccessor
{
public IClientSessionHandle? CurrentSession => null;

View File

@@ -23,9 +23,9 @@ using StellaOps.Authority.OpenIddict.Handlers;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.RateLimiting;
using StellaOps.Authority.Airgap;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Cryptography.Audit;
using StellaOps.Configuration;
using StellaOps.Auth.Abstractions;

View File

@@ -5,9 +5,9 @@ using Microsoft.Extensions.Time.Testing;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using StellaOps.Authority.OpenIddict.Handlers;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using Xunit;
namespace StellaOps.Authority.Tests.OpenIddict;
@@ -22,7 +22,7 @@ public sealed class TokenPersistenceIntegrationTests
var issuedAt = new DateTimeOffset(2025, 10, 10, 12, 0, 0, TimeSpan.Zero);
var clock = new FakeTimeProvider(issuedAt);
var tokenStore = new InMemoryTokenStore();
var handler = new PersistTokensHandler(tokenStore, new NullAuthorityMongoSessionAccessor(), clock, Activity, NullLogger<PersistTokensHandler>.Instance);
var handler = new PersistTokensHandler(tokenStore, new NullAuthoritySessionAccessor(), clock, Activity, NullLogger<PersistTokensHandler>.Instance);
var identity = new ClaimsIdentity(authenticationType: "test");
identity.SetClaim(OpenIddictConstants.Claims.Subject, "subject-1");

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Airgap;

View File

@@ -5,8 +5,8 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Audit;

View File

@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Bootstrap;

View File

@@ -10,8 +10,8 @@ using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Authority.Console;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Observability;

View File

@@ -17,9 +17,9 @@ using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Airgap;
using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.RateLimiting;
using StellaOps.Authority.Security;
using StellaOps.Configuration;
@@ -1522,7 +1522,7 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
{
private readonly IAuthorityIdentityProviderRegistry registry;
private readonly IAuthorityTokenStore tokenStore;
private readonly IAuthorityMongoSessionAccessor sessionAccessor;
private readonly IAuthoritySessionAccessor sessionAccessor;
private readonly IAuthorityRateLimiterMetadataAccessor metadataAccessor;
private readonly TimeProvider clock;
private readonly ActivitySource activitySource;
@@ -1531,7 +1531,7 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
public HandleClientCredentialsHandler(
IAuthorityIdentityProviderRegistry registry,
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
IAuthorityRateLimiterMetadataAccessor metadataAccessor,
TimeProvider clock,
ActivitySource activitySource,

View File

@@ -19,8 +19,8 @@ using StellaOps.Authority.OpenIddict;
using StellaOps.Auth.Abstractions;
using StellaOps.Authority.RateLimiting;
using StellaOps.Authority.Security;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Cryptography.Audit;
using Microsoft.IdentityModel.Tokens;

View File

@@ -15,8 +15,8 @@ using StellaOps.Authority.Airgap;
using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.RateLimiting;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.OpenIddict.Handlers;

View File

@@ -11,8 +11,8 @@ using OpenIddict.Server;
using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Airgap;
using StellaOps.Authority.Security;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.OpenIddict.Handlers;

View File

@@ -6,22 +6,22 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Server;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.OpenIddict.Handlers;
internal sealed class HandleRevocationRequestHandler : IOpenIddictServerHandler<OpenIddictServerEvents.HandleRevocationRequestContext>
{
private readonly IAuthorityTokenStore tokenStore;
private readonly IAuthorityMongoSessionAccessor sessionAccessor;
private readonly IAuthoritySessionAccessor sessionAccessor;
private readonly TimeProvider clock;
private readonly ILogger<HandleRevocationRequestHandler> logger;
private readonly ActivitySource activitySource;
public HandleRevocationRequestHandler(
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
TimeProvider clock,
ActivitySource activitySource,
ILogger<HandleRevocationRequestHandler> logger)

View File

@@ -11,9 +11,9 @@ using Microsoft.Extensions.Logging;
using OpenIddict.Abstractions;
using OpenIddict.Extensions;
using OpenIddict.Server;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Auth.Abstractions;
namespace StellaOps.Authority.OpenIddict.Handlers;
@@ -21,14 +21,14 @@ namespace StellaOps.Authority.OpenIddict.Handlers;
internal sealed class PersistTokensHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ProcessSignInContext>
{
private readonly IAuthorityTokenStore tokenStore;
private readonly IAuthorityMongoSessionAccessor sessionAccessor;
private readonly IAuthoritySessionAccessor sessionAccessor;
private readonly TimeProvider clock;
private readonly ActivitySource activitySource;
private readonly ILogger<PersistTokensHandler> logger;
public PersistTokensHandler(
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
TimeProvider clock,
ActivitySource activitySource,
ILogger<PersistTokensHandler> logger)

View File

@@ -15,9 +15,9 @@ using StellaOps.Auth.Abstractions;
using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.RateLimiting;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit;
using StellaOps.Authority.Security;
@@ -26,7 +26,7 @@ namespace StellaOps.Authority.OpenIddict.Handlers;
internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ValidateTokenContext>
{
private readonly IAuthorityTokenStore tokenStore;
private readonly IAuthorityMongoSessionAccessor sessionAccessor;
private readonly IAuthoritySessionAccessor sessionAccessor;
private readonly IAuthorityClientStore clientStore;
private readonly IAuthorityIdentityProviderRegistry registry;
private readonly IAuthorityRateLimiterMetadataAccessor metadataAccessor;
@@ -40,7 +40,7 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
public ValidateAccessTokenHandler(
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
IAuthorityClientStore clientStore,
IAuthorityIdentityProviderRegistry registry,
IAuthorityRateLimiterMetadataAccessor metadataAccessor,

View File

@@ -32,9 +32,9 @@ using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugins;
using StellaOps.Authority.Bootstrap;
using StellaOps.Authority.Console;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Postgres;
using StellaOps.Authority.Storage.PostgresAdapters;
using StellaOps.Authority.RateLimiting;
@@ -54,7 +54,7 @@ using System.Text;
using StellaOps.Authority.Signing;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Kms;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Security;
using StellaOps.Authority.OpenApi;
using StellaOps.Auth.Abstractions;
@@ -249,7 +249,7 @@ builder.Services.AddAuthorityPostgresStorage(options =>
options.AutoMigrate = true;
options.MigrationsPath = "Migrations";
});
builder.Services.TryAddSingleton<IAuthorityMongoSessionAccessor, NullAuthorityMongoSessionAccessor>();
builder.Services.TryAddSingleton<IAuthoritySessionAccessor, NullAuthoritySessionAccessor>();
builder.Services.TryAddScoped<IAuthorityBootstrapInviteStore, PostgresBootstrapInviteStore>();
builder.Services.TryAddScoped<IAuthorityServiceAccountStore, PostgresServiceAccountStore>();
builder.Services.TryAddScoped<IAuthorityClientStore, PostgresClientStore>();
@@ -1325,7 +1325,7 @@ if (authorityOptions.Bootstrap.Enabled)
string accountId,
IAuthorityServiceAccountStore accountStore,
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
CancellationToken cancellationToken) =>
{
if (string.IsNullOrWhiteSpace(accountId))
@@ -1355,7 +1355,7 @@ if (authorityOptions.Bootstrap.Enabled)
HttpContext httpContext,
IAuthorityServiceAccountStore accountStore,
IAuthorityTokenStore tokenStore,
IAuthorityMongoSessionAccessor sessionAccessor,
IAuthoritySessionAccessor sessionAccessor,
IAuthEventSink auditSink,
TimeProvider timeProvider,
CancellationToken cancellationToken) =>

View File

@@ -10,8 +10,8 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Configuration;
namespace StellaOps.Authority.Revocation;

View File

@@ -1,5 +1,5 @@
using System;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.InMemory.Documents;
namespace StellaOps.Authority.Security;

View File

@@ -9,7 +9,7 @@ using System.Formats.Asn1;
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Configuration;
using Microsoft.IdentityModel.Tokens;

View File

@@ -1,7 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.InMemory.Documents;
namespace StellaOps.Authority.Security;

View File

@@ -22,7 +22,7 @@
<PackageReference Include="YamlDotNet" Version="13.7.1" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Plugin.Standard\StellaOps.Authority.Plugin.Standard.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Storage.Mongo\StellaOps.Authority.Storage.Mongo.csproj" />
<ProjectReference Include="..\StellaOps.Authority.Storage.InMemory\StellaOps.Authority.Storage.InMemory.csproj" />
<ProjectReference Include="..\..\__Libraries\StellaOps.Authority.Storage.Postgres\StellaOps.Authority.Storage.Postgres.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj" />

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,7 +1,7 @@
using System.Globalization;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,6 +1,6 @@
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -1,8 +1,8 @@
using System.Collections.Concurrent;
using System.Text.Json;
using StellaOps.Authority.Storage.Mongo.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores;
using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;

View File

@@ -65,7 +65,7 @@ using StellaOps.Aoc.AspNetCore.Results;
using HttpResults = Microsoft.AspNetCore.Http.Results;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.Concelier.Storage.Aliases;
using StellaOps.Provenance.Mongo;
using StellaOps.Provenance;
namespace StellaOps.Concelier.WebService
{

View File

@@ -10,8 +10,8 @@ using System.Xml.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Bson.IO;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Documents.IO;
using StellaOps.Concelier.Connector.Acsc.Configuration;
using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Connector.Common.Fetch;
@@ -292,7 +292,7 @@ public sealed class AcscConnector : IFeedConnector
var dto = AcscFeedParser.Parse(rawBytes, metadata.FeedSlug, parsedAt, _htmlSanitizer);
var json = JsonSerializer.Serialize(dto, SerializerOptions);
var payload = BsonDocument.Parse(json);
var payload = DocumentObject.Parse(json);
var existingDto = await _dtoStore.FindByDocumentIdAsync(document.Id, cancellationToken).ConfigureAwait(false);
var dtoRecord = existingDto is null
@@ -678,7 +678,7 @@ public sealed class AcscConnector : IFeedConnector
private Task UpdateCursorAsync(AcscCursor cursor, CancellationToken cancellationToken)
{
var document = cursor.ToBsonDocument();
var document = cursor.ToDocumentObject();
var completedAt = _timeProvider.GetUtcNow();
return _stateRepository.UpdateCursorAsync(SourceName, document, completedAt, cancellationToken);
}

View File

@@ -1,4 +1,4 @@
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Acsc.Internal;
@@ -48,16 +48,16 @@ internal sealed record AcscCursor(
return this with { LastPublishedByFeed = snapshot };
}
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["preferredEndpoint"] = PreferredEndpoint.ToString(),
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
var feedsDocument = new BsonDocument();
var feedsDocument = new DocumentObject();
foreach (var kvp in LastPublishedByFeed)
{
if (kvp.Value.HasValue)
@@ -70,7 +70,7 @@ internal sealed record AcscCursor(
return document;
}
public static AcscCursor FromBson(BsonDocument? document)
public static AcscCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -82,7 +82,7 @@ internal sealed record AcscCursor(
: AcscEndpointPreference.Auto;
var feeds = new Dictionary<string, DateTimeOffset?>(StringComparer.OrdinalIgnoreCase);
if (document.TryGetValue("feeds", out var feedsValue) && feedsValue is BsonDocument feedsDocument)
if (document.TryGetValue("feeds", out var feedsValue) && feedsValue is DocumentObject feedsDocument)
{
foreach (var element in feedsDocument.Elements)
{
@@ -100,9 +100,9 @@ internal sealed record AcscCursor(
pendingMappings);
}
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}
@@ -119,12 +119,12 @@ internal sealed record AcscCursor(
return list;
}
private static DateTimeOffset? ParseDate(BsonValue value)
private static DateTimeOffset? ParseDate(DocumentValue value)
{
return value.BsonType switch
return value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
using System.Globalization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.Cccs.Configuration;
using StellaOps.Concelier.Connector.Cccs.Internal;
using StellaOps.Concelier.Connector.Common;
@@ -332,7 +332,7 @@ public sealed class CccsConnector : IFeedConnector
}
var dtoJson = JsonSerializer.Serialize(dto, DtoSerializerOptions);
var dtoBson = BsonDocument.Parse(dtoJson);
var dtoBson = DocumentObject.Parse(dtoJson);
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, SourceName, DtoSchemaVersion, dtoBson, now);
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.PendingMap, cancellationToken).ConfigureAwait(false);
@@ -464,7 +464,7 @@ public sealed class CccsConnector : IFeedConnector
private Task UpdateCursorAsync(CccsCursor cursor, CancellationToken cancellationToken)
{
var document = cursor.ToBsonDocument();
var document = cursor.ToDocumentObject();
var completedAt = cursor.LastFetchAt ?? _timeProvider.GetUtcNow();
return _stateRepository.UpdateCursorAsync(SourceName, document, completedAt, cancellationToken);
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Cccs.Internal;
@@ -39,20 +39,20 @@ internal sealed record CccsCursor(
public CccsCursor WithLastFetch(DateTimeOffset? timestamp)
=> this with { LastFetchAt = timestamp };
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var doc = new BsonDocument
var doc = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
if (KnownEntryHashes.Count > 0)
{
var hashes = new BsonArray();
var hashes = new DocumentArray();
foreach (var kvp in KnownEntryHashes)
{
hashes.Add(new BsonDocument
hashes.Add(new DocumentObject
{
["uri"] = kvp.Key,
["hash"] = kvp.Value,
@@ -70,7 +70,7 @@ internal sealed record CccsCursor(
return doc;
}
public static CccsCursor FromBson(BsonDocument? document)
public static CccsCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -87,9 +87,9 @@ internal sealed record CccsCursor(
return new CccsCursor(pendingDocuments, pendingMappings, hashes, lastFetch);
}
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidCollection;
}
@@ -106,9 +106,9 @@ internal sealed record CccsCursor(
return items;
}
private static IReadOnlyDictionary<string, string> ReadHashMap(BsonDocument document)
private static IReadOnlyDictionary<string, string> ReadHashMap(DocumentObject document)
{
if (!document.TryGetValue("knownEntryHashes", out var value) || value is not BsonArray array || array.Count == 0)
if (!document.TryGetValue("knownEntryHashes", out var value) || value is not DocumentArray array || array.Count == 0)
{
return EmptyHashes;
}
@@ -116,17 +116,17 @@ internal sealed record CccsCursor(
var map = new Dictionary<string, string>(array.Count, StringComparer.Ordinal);
foreach (var element in array)
{
if (element is not BsonDocument entry)
if (element is not DocumentObject entry)
{
continue;
}
if (!entry.TryGetValue("uri", out var uriValue) || uriValue.IsBsonNull || string.IsNullOrWhiteSpace(uriValue.AsString))
if (!entry.TryGetValue("uri", out var uriValue) || uriValue.IsDocumentNull || string.IsNullOrWhiteSpace(uriValue.AsString))
{
continue;
}
var hash = entry.TryGetValue("hash", out var hashValue) && !hashValue.IsBsonNull
var hash = entry.TryGetValue("hash", out var hashValue) && !hashValue.IsDocumentNull
? hashValue.AsString
: string.Empty;
map[uriValue.AsString] = hash;
@@ -135,11 +135,11 @@ internal sealed record CccsCursor(
return map;
}
private static DateTimeOffset? ParseDateTime(BsonValue value)
=> value.BsonType switch
private static DateTimeOffset? ParseDateTime(DocumentValue value)
=> value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -6,7 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.CertBund.Configuration;
using StellaOps.Concelier.Connector.CertBund.Internal;
using StellaOps.Concelier.Connector.Common;
@@ -286,7 +286,7 @@ public sealed class CertBundConnector : IFeedConnector
_diagnostics.ParseSuccess(dto.Products.Count, dto.CveIds.Count);
parsedCount++;
var bson = BsonDocument.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var bson = DocumentObject.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "cert-bund.detail.v1", bson, now);
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
await _documentStore.UpdateStatusAsync(document.Id, DocumentStatuses.PendingMap, cancellationToken).ConfigureAwait(false);
@@ -428,7 +428,7 @@ public sealed class CertBundConnector : IFeedConnector
private Task UpdateCursorAsync(CertBundCursor cursor, CancellationToken cancellationToken)
{
var document = cursor.ToBsonDocument();
var document = cursor.ToDocumentObject();
var completedAt = cursor.LastFetchAt ?? _timeProvider.GetUtcNow();
return _stateRepository.UpdateCursorAsync(SourceName, document, completedAt, cancellationToken);
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.CertBund.Internal;
@@ -31,13 +31,13 @@ internal sealed record CertBundCursor(
public CertBundCursor WithLastFetch(DateTimeOffset? timestamp)
=> this with { LastFetchAt = timestamp };
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["knownAdvisories"] = new BsonArray(KnownAdvisories),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
["knownAdvisories"] = new DocumentArray(KnownAdvisories),
};
if (LastPublished.HasValue)
@@ -53,7 +53,7 @@ internal sealed record CertBundCursor(
return document;
}
public static CertBundCursor FromBson(BsonDocument? document)
public static CertBundCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -76,9 +76,9 @@ internal sealed record CertBundCursor(
private static IReadOnlyCollection<Guid> Distinct(IEnumerable<Guid>? values)
=> values?.Distinct().ToArray() ?? EmptyGuids;
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuids;
}
@@ -95,9 +95,9 @@ internal sealed record CertBundCursor(
return items;
}
private static IReadOnlyCollection<string> ReadStringArray(BsonDocument document, string field)
private static IReadOnlyCollection<string> ReadStringArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyStrings;
}
@@ -108,11 +108,11 @@ internal sealed record CertBundCursor(
.ToArray();
}
private static DateTimeOffset? ParseDate(BsonValue value)
=> value.BsonType switch
private static DateTimeOffset? ParseDate(DocumentValue value)
=> value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -9,7 +9,7 @@ using System.Text.Json.Serialization;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.CertCc.Configuration;
using StellaOps.Concelier.Connector.CertCc.Internal;
@@ -338,7 +338,7 @@ public sealed class CertCcConnector : IFeedConnector
var dto = CertCcNoteParser.Parse(noteBytes, vendorsBytes, vulsBytes, vendorStatusesBytes);
var json = JsonSerializer.Serialize(dto, DtoSerializerOptions);
var payload = StellaOps.Concelier.Bson.BsonDocument.Parse(json);
var payload = StellaOps.Concelier.Documents.DocumentObject.Parse(json);
_diagnostics.ParseSuccess(
dto.Vendors.Count,
@@ -678,7 +678,7 @@ public sealed class CertCcConnector : IFeedConnector
private async Task UpdateCursorAsync(CertCcCursor cursor, CancellationToken cancellationToken)
{
var completedAt = _timeProvider.GetUtcNow();
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToBsonDocument(), completedAt, cancellationToken).ConfigureAwait(false);
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), completedAt, cancellationToken).ConfigureAwait(false);
}
private sealed class NoteDocumentGroup

View File

@@ -1,4 +1,4 @@
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.Common.Cursors;
namespace StellaOps.Concelier.Connector.CertCc.Internal;
@@ -22,18 +22,18 @@ internal sealed record CertCcCursor(
EmptyGuidArray,
null);
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument();
var document = new DocumentObject();
var summary = new BsonDocument();
var summary = new DocumentObject();
SummaryState.WriteTo(summary, "start", "end");
document["summary"] = summary;
document["pendingSummaries"] = new BsonArray(PendingSummaries.Select(static id => id.ToString()));
document["pendingNotes"] = new BsonArray(PendingNotes.Select(static note => note));
document["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString()));
document["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString()));
document["pendingSummaries"] = new DocumentArray(PendingSummaries.Select(static id => id.ToString()));
document["pendingNotes"] = new DocumentArray(PendingNotes.Select(static note => note));
document["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(static id => id.ToString()));
document["pendingMappings"] = new DocumentArray(PendingMappings.Select(static id => id.ToString()));
if (LastRun.HasValue)
{
@@ -43,7 +43,7 @@ internal sealed record CertCcCursor(
return document;
}
public static CertCcCursor FromBson(BsonDocument? document)
public static CertCcCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -51,9 +51,9 @@ internal sealed record CertCcCursor(
}
TimeWindowCursorState summaryState = TimeWindowCursorState.Empty;
if (document.TryGetValue("summary", out var summaryValue) && summaryValue is BsonDocument summaryDocument)
if (document.TryGetValue("summary", out var summaryValue) && summaryValue is DocumentObject summaryDocument)
{
summaryState = TimeWindowCursorState.FromBsonDocument(summaryDocument, "start", "end");
summaryState = TimeWindowCursorState.FromDocumentObject(summaryDocument, "start", "end");
}
var pendingSummaries = ReadGuidArray(document, "pendingSummaries");
@@ -64,10 +64,10 @@ internal sealed record CertCcCursor(
DateTimeOffset? lastRun = null;
if (document.TryGetValue("lastRun", out var lastRunValue))
{
lastRun = lastRunValue.BsonType switch
lastRun = lastRunValue.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(lastRunValue.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(lastRunValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(lastRunValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(lastRunValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
@@ -93,9 +93,9 @@ internal sealed record CertCcCursor(
public CertCcCursor WithLastRun(DateTimeOffset? timestamp)
=> this with { LastRun = timestamp };
private static Guid[] ReadGuidArray(BsonDocument document, string field)
private static Guid[] ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array || array.Count == 0)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array || array.Count == 0)
{
return EmptyGuidArray;
}
@@ -112,9 +112,9 @@ internal sealed record CertCcCursor(
return results.Count == 0 ? EmptyGuidArray : results.Distinct().ToArray();
}
private static string[] ReadStringArray(BsonDocument document, string field)
private static string[] ReadStringArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array || array.Count == 0)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array || array.Count == 0)
{
return EmptyStringArray;
}
@@ -124,10 +124,10 @@ internal sealed record CertCcCursor(
{
switch (element)
{
case BsonString bsonString when !string.IsNullOrWhiteSpace(bsonString.AsString):
case DocumentString bsonString when !string.IsNullOrWhiteSpace(bsonString.AsString):
results.Add(bsonString.AsString.Trim());
break;
case BsonDocument bsonDocument when bsonDocument.TryGetValue("value", out var inner) && inner.IsString:
case DocumentObject bsonDocument when bsonDocument.TryGetValue("value", out var inner) && inner.IsString:
results.Add(inner.AsString.Trim());
break;
}
@@ -142,14 +142,14 @@ internal sealed record CertCcCursor(
.ToArray();
}
private static bool TryReadGuid(BsonValue value, out Guid guid)
private static bool TryReadGuid(DocumentValue value, out Guid guid)
{
if (value is BsonString bsonString && Guid.TryParse(bsonString.AsString, out guid))
if (value is DocumentString bsonString && Guid.TryParse(bsonString.AsString, out guid))
{
return true;
}
if (value is BsonBinaryData binary)
if (value is DocumentBinaryData binary)
{
try
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.CertFr.Configuration;
using StellaOps.Concelier.Connector.CertFr.Internal;
using StellaOps.Concelier.Connector.Common;
@@ -236,7 +236,7 @@ public sealed class CertFrConnector : IFeedConnector
}
var json = JsonSerializer.Serialize(dto, SerializerOptions);
var payload = BsonDocument.Parse(json);
var payload = DocumentObject.Parse(json);
var validatedAt = _timeProvider.GetUtcNow();
var existingDto = await _dtoStore.FindByDocumentIdAsync(document.Id, cancellationToken).ConfigureAwait(false);
@@ -332,6 +332,6 @@ public sealed class CertFrConnector : IFeedConnector
private async Task UpdateCursorAsync(CertFrCursor cursor, CancellationToken cancellationToken)
{
var completedAt = _timeProvider.GetUtcNow();
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToBsonDocument(), completedAt, cancellationToken).ConfigureAwait(false);
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), completedAt, cancellationToken).ConfigureAwait(false);
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.CertFr.Internal;
@@ -12,12 +12,12 @@ internal sealed record CertFrCursor(
{
public static CertFrCursor Empty { get; } = new(null, Array.Empty<Guid>(), Array.Empty<Guid>());
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
if (LastPublished.HasValue)
@@ -28,7 +28,7 @@ internal sealed record CertFrCursor(
return document;
}
public static CertFrCursor FromBson(BsonDocument? document)
public static CertFrCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -54,17 +54,17 @@ internal sealed record CertFrCursor(
public CertFrCursor WithPendingMappings(IEnumerable<Guid> ids)
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() };
private static DateTimeOffset? ParseDate(BsonValue value)
=> value.BsonType switch
private static DateTimeOffset? ParseDate(DocumentValue value)
=> value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var raw) || raw is not BsonArray array)
if (!document.TryGetValue(field, out var raw) || raw is not DocumentArray array)
{
return Array.Empty<Guid>();
}

View File

@@ -6,7 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.CertIn.Configuration;
using StellaOps.Concelier.Connector.CertIn.Internal;
@@ -226,7 +226,7 @@ public sealed class CertInConnector : IFeedConnector
}
var dto = CertInDetailParser.Parse(listing, rawBytes);
var payload = BsonDocument.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var payload = DocumentObject.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "certin.v1", payload, _timeProvider.GetUtcNow());
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
@@ -271,9 +271,9 @@ public sealed class CertInConnector : IFeedConnector
continue;
}
var dtoJson = dtoRecord.Payload.ToJson(new StellaOps.Concelier.Bson.IO.JsonWriterSettings
var dtoJson = dtoRecord.Payload.ToJson(new StellaOps.Concelier.Documents.IO.JsonWriterSettings
{
OutputMode = StellaOps.Concelier.Bson.IO.JsonOutputMode.RelaxedExtendedJson,
OutputMode = StellaOps.Concelier.Documents.IO.JsonOutputMode.RelaxedExtendedJson,
});
CertInAdvisoryDto dto;
@@ -423,7 +423,7 @@ public sealed class CertInConnector : IFeedConnector
private Task UpdateCursorAsync(CertInCursor cursor, CancellationToken cancellationToken)
{
return _stateRepository.UpdateCursorAsync(SourceName, cursor.ToBsonDocument(), _timeProvider.GetUtcNow(), cancellationToken);
return _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), _timeProvider.GetUtcNow(), cancellationToken);
}
private static bool TryDeserializeListing(IReadOnlyDictionary<string, string>? metadata, out CertInListingItem listing)

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.CertIn.Internal;
@@ -12,12 +12,12 @@ internal sealed record CertInCursor(
{
public static CertInCursor Empty { get; } = new(null, Array.Empty<Guid>(), Array.Empty<Guid>());
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
if (LastPublished.HasValue)
@@ -28,7 +28,7 @@ internal sealed record CertInCursor(
return document;
}
public static CertInCursor FromBson(BsonDocument? document)
public static CertInCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -54,17 +54,17 @@ internal sealed record CertInCursor(
public CertInCursor WithPendingMappings(IEnumerable<Guid> ids)
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() };
private static DateTimeOffset? ParseDate(BsonValue value)
=> value.BsonType switch
private static DateTimeOffset? ParseDate(DocumentValue value)
=> value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return Array.Empty<Guid>();
}

View File

@@ -1,4 +1,4 @@
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Common.Cursors;
@@ -14,14 +14,14 @@ public sealed record TimeWindowCursorState(DateTimeOffset? LastWindowStart, Date
return new TimeWindowCursorState(window.Start, window.End);
}
public BsonDocument ToBsonDocument(string startField = "windowStart", string endField = "windowEnd")
public DocumentObject ToDocumentObject(string startField = "windowStart", string endField = "windowEnd")
{
var document = new BsonDocument();
var document = new DocumentObject();
WriteTo(document, startField, endField);
return document;
}
public void WriteTo(BsonDocument document, string startField = "windowStart", string endField = "windowEnd")
public void WriteTo(DocumentObject document, string startField = "windowStart", string endField = "windowEnd")
{
ArgumentNullException.ThrowIfNull(document);
ArgumentException.ThrowIfNullOrEmpty(startField);
@@ -41,7 +41,7 @@ public sealed record TimeWindowCursorState(DateTimeOffset? LastWindowStart, Date
}
}
public static TimeWindowCursorState FromBsonDocument(BsonDocument? document, string startField = "windowStart", string endField = "windowEnd")
public static TimeWindowCursorState FromDocumentObject(DocumentObject? document, string startField = "windowStart", string endField = "windowEnd")
{
if (document is null)
{
@@ -64,12 +64,12 @@ public sealed record TimeWindowCursorState(DateTimeOffset? LastWindowStart, Date
return new TimeWindowCursorState(start, end);
}
private static DateTimeOffset? ReadDateTimeOffset(BsonValue value)
private static DateTimeOffset? ReadDateTimeOffset(DocumentValue value)
{
return value.BsonType switch
return value.DocumentType switch
{
BsonType.DateTime => new DateTimeOffset(value.ToUniversalTime(), TimeSpan.Zero),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => new DateTimeOffset(value.ToUniversalTime(), TimeSpan.Zero),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -9,7 +9,7 @@ using System.Net.Http.Headers;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using MongoContracts = StellaOps.Concelier.Storage;
using StorageContracts = StellaOps.Concelier.Storage.Contracts;
using StellaOps.Concelier.Connector.Common.Http;
@@ -37,7 +37,7 @@ public sealed class SourceFetchService
private readonly ILogger<SourceFetchService> _logger;
private readonly TimeProvider _timeProvider;
private readonly IOptionsMonitor<SourceHttpClientOptions> _httpClientOptions;
private readonly IOptions<MongoContracts.MongoStorageOptions> _storageOptions;
private readonly IOptions<MongoContracts.StorageOptions> _storageOptions;
private readonly IJitterSource _jitterSource;
private readonly IAdvisoryRawWriteGuard _guard;
private readonly IAdvisoryLinksetMapper _linksetMapper;
@@ -56,7 +56,7 @@ public sealed class SourceFetchService
ICryptoHash hash,
TimeProvider? timeProvider = null,
IOptionsMonitor<SourceHttpClientOptions>? httpClientOptions = null,
IOptions<MongoContracts.MongoStorageOptions>? storageOptions = null)
IOptions<MongoContracts.StorageOptions>? storageOptions = null)
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
@@ -85,7 +85,7 @@ public sealed class SourceFetchService
ICryptoHash hash,
TimeProvider? timeProvider = null,
IOptionsMonitor<SourceHttpClientOptions>? httpClientOptions = null,
IOptions<MongoContracts.MongoStorageOptions>? storageOptions = null)
IOptions<MongoContracts.StorageOptions>? storageOptions = null)
: this(
httpClientFactory,
rawDocumentStorage,

View File

@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.Common.Fetch;
using MongoContracts = StellaOps.Concelier.Storage;
using StellaOps.Cryptography;
@@ -62,7 +62,7 @@ public sealed class SourceStateSeedProcessor
}
var state = await _stateRepository.TryGetAsync(specification.Source, cancellationToken).ConfigureAwait(false);
var cursor = state?.Cursor ?? new BsonDocument();
var cursor = state?.Cursor ?? new DocumentObject();
var newlyPendingDocuments = MergeGuidArray(cursor, "pendingDocuments", pendingDocumentIds);
var newlyPendingMappings = MergeGuidArray(cursor, "pendingMappings", pendingMappingIds);
@@ -216,14 +216,14 @@ public sealed class SourceStateSeedProcessor
return new Dictionary<string, string>(values, StringComparer.OrdinalIgnoreCase);
}
private static IReadOnlyCollection<Guid> MergeGuidArray(BsonDocument cursor, string field, IReadOnlyCollection<Guid> additions)
private static IReadOnlyCollection<Guid> MergeGuidArray(DocumentObject cursor, string field, IReadOnlyCollection<Guid> additions)
{
if (additions.Count == 0)
{
return Array.Empty<Guid>();
}
var existing = cursor.TryGetValue(field, out var value) && value is BsonArray existingArray
var existing = cursor.TryGetValue(field, out var value) && value is DocumentArray existingArray
? existingArray.Select(AsGuid).Where(static g => g != Guid.Empty).ToHashSet()
: new HashSet<Guid>();
@@ -243,7 +243,7 @@ public sealed class SourceStateSeedProcessor
if (existing.Count > 0)
{
cursor[field] = new BsonArray(existing
cursor[field] = new DocumentArray(existing
.Select(static g => g.ToString("D"))
.OrderBy(static s => s, StringComparer.OrdinalIgnoreCase));
}
@@ -251,14 +251,14 @@ public sealed class SourceStateSeedProcessor
return newlyAdded.AsReadOnly();
}
private static IReadOnlyCollection<string> MergeStringArray(BsonDocument cursor, string field, IReadOnlyCollection<string> additions)
private static IReadOnlyCollection<string> MergeStringArray(DocumentObject cursor, string field, IReadOnlyCollection<string> additions)
{
if (additions.Count == 0)
{
return Array.Empty<string>();
}
var existing = cursor.TryGetValue(field, out var value) && value is BsonArray existingArray
var existing = cursor.TryGetValue(field, out var value) && value is DocumentArray existingArray
? existingArray.Select(static v => v?.AsString ?? string.Empty)
.Where(static s => !string.IsNullOrWhiteSpace(s))
.ToHashSet(StringComparer.OrdinalIgnoreCase)
@@ -281,14 +281,14 @@ public sealed class SourceStateSeedProcessor
if (existing.Count > 0)
{
cursor[field] = new BsonArray(existing
cursor[field] = new DocumentArray(existing
.OrderBy(static s => s, StringComparer.OrdinalIgnoreCase));
}
return newlyAdded.AsReadOnly();
}
private static Guid AsGuid(BsonValue value)
private static Guid AsGuid(DocumentValue value)
{
if (value is null)
{

View File

@@ -8,7 +8,7 @@ using System.Text.Json;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Normalization.Text;
using StellaOps.Concelier.Connector.Common;
@@ -377,7 +377,7 @@ public sealed class CveConnector : IFeedConnector
continue;
}
var payload = BsonDocument.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var payload = DocumentObject.Parse(JsonSerializer.Serialize(dto, SerializerOptions));
var dtoRecord = new DtoRecord(
Guid.NewGuid(),
document.Id,
@@ -576,7 +576,7 @@ public sealed class CveConnector : IFeedConnector
private async Task UpdateCursorAsync(CveCursor cursor, CancellationToken cancellationToken)
{
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToBsonDocument(), _timeProvider.GetUtcNow(), cancellationToken).ConfigureAwait(false);
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), _timeProvider.GetUtcNow(), cancellationToken).ConfigureAwait(false);
}
private static Uri BuildListRequestUri(DateTimeOffset since, DateTimeOffset until, int page, int pageSize)

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Cve.Internal;
@@ -22,13 +22,13 @@ internal sealed record CveCursor(
PendingDocuments: EmptyGuidList,
PendingMappings: EmptyGuidList);
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["nextPage"] = NextPage,
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
};
if (LastModifiedExclusive.HasValue)
@@ -49,7 +49,7 @@ internal sealed record CveCursor(
return document;
}
public static CveCursor FromBson(BsonDocument? document)
public static CveCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -99,19 +99,19 @@ internal sealed record CveCursor(
public CveCursor WithNextPage(int page)
=> this with { NextPage = page < 1 ? 1 : page };
private static DateTimeOffset? ParseDate(BsonValue value)
private static DateTimeOffset? ParseDate(DocumentValue value)
{
return value.BsonType switch
return value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}

View File

@@ -7,8 +7,8 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Bson.IO;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Documents.IO;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
@@ -443,7 +443,7 @@ public sealed class DebianConnector : IFeedConnector
private async Task UpdateCursorAsync(DebianCursor cursor, CancellationToken cancellationToken)
{
var document = cursor.ToBsonDocument();
var document = cursor.ToDocumentObject();
await _stateRepository.UpdateCursorAsync(SourceName, document, _timeProvider.GetUtcNow(), cancellationToken).ConfigureAwait(false);
}
@@ -508,12 +508,12 @@ public sealed class DebianConnector : IFeedConnector
cveList);
}
private static BsonDocument ToBson(DebianAdvisoryDto dto)
private static DocumentObject ToBson(DebianAdvisoryDto dto)
{
var packages = new BsonArray();
var packages = new DocumentArray();
foreach (var package in dto.Packages)
{
var packageDoc = new BsonDocument
var packageDoc = new DocumentObject
{
["package"] = package.Package,
["release"] = package.Release,
@@ -543,9 +543,9 @@ public sealed class DebianConnector : IFeedConnector
packages.Add(packageDoc);
}
var references = new BsonArray(dto.References.Select(reference =>
var references = new DocumentArray(dto.References.Select(reference =>
{
var doc = new BsonDocument
var doc = new DocumentObject
{
["url"] = reference.Url
};
@@ -563,27 +563,27 @@ public sealed class DebianConnector : IFeedConnector
return doc;
}));
return new BsonDocument
return new DocumentObject
{
["advisoryId"] = dto.AdvisoryId,
["sourcePackage"] = dto.SourcePackage,
["title"] = dto.Title,
["description"] = dto.Description ?? string.Empty,
["cves"] = new BsonArray(dto.CveIds),
["cves"] = new DocumentArray(dto.CveIds),
["packages"] = packages,
["references"] = references,
};
}
private static DebianAdvisoryDto FromBson(BsonDocument document)
private static DebianAdvisoryDto FromBson(DocumentObject document)
{
var advisoryId = document.GetValue("advisoryId", "").AsString;
var sourcePackage = document.GetValue("sourcePackage", advisoryId).AsString;
var title = document.GetValue("title", advisoryId).AsString;
var description = document.TryGetValue("description", out var desc) ? desc.AsString : null;
var cves = document.TryGetValue("cves", out var cveArray) && cveArray is BsonArray cvesBson
? cvesBson.OfType<BsonValue>()
var cves = document.TryGetValue("cves", out var cveArray) && cveArray is DocumentArray cvesBson
? cvesBson.OfType<DocumentValue>()
.Select(static value => value.ToString())
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s!)
@@ -591,9 +591,9 @@ public sealed class DebianConnector : IFeedConnector
: Array.Empty<string>();
var packages = new List<DebianPackageStateDto>();
if (document.TryGetValue("packages", out var packageArray) && packageArray is BsonArray packagesBson)
if (document.TryGetValue("packages", out var packageArray) && packageArray is DocumentArray packagesBson)
{
foreach (var element in packagesBson.OfType<BsonDocument>())
foreach (var element in packagesBson.OfType<DocumentObject>())
{
packages.Add(new DebianPackageStateDto(
element.GetValue("package", sourcePackage).AsString,
@@ -603,10 +603,10 @@ public sealed class DebianConnector : IFeedConnector
element.TryGetValue("fixed", out var fixedValue) ? fixedValue.AsString : null,
element.TryGetValue("last", out var lastValue) ? lastValue.AsString : null,
element.TryGetValue("published", out var publishedValue)
? publishedValue.BsonType switch
? publishedValue.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(publishedValue.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(publishedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(publishedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(publishedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => (DateTimeOffset?)null,
}
: null));
@@ -614,9 +614,9 @@ public sealed class DebianConnector : IFeedConnector
}
var references = new List<DebianReferenceDto>();
if (document.TryGetValue("references", out var referenceArray) && referenceArray is BsonArray refBson)
if (document.TryGetValue("references", out var referenceArray) && referenceArray is DocumentArray refBson)
{
foreach (var element in refBson.OfType<BsonDocument>())
foreach (var element in refBson.OfType<DocumentObject>())
{
references.Add(new DebianReferenceDto(
element.GetValue("url", "").AsString,

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Distro.Debian.Internal;
@@ -19,7 +19,7 @@ internal sealed record DebianCursor(
public static DebianCursor Empty { get; } = new(null, EmptyIds, EmptyGuidList, EmptyGuidList, EmptyCache);
public static DebianCursor FromBson(BsonDocument? document)
public static DebianCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -29,10 +29,10 @@ internal sealed record DebianCursor(
DateTimeOffset? lastPublished = null;
if (document.TryGetValue("lastPublished", out var lastValue))
{
lastPublished = lastValue.BsonType switch
lastPublished = lastValue.DocumentType switch
{
BsonType.String when DateTimeOffset.TryParse(lastValue.AsString, out var parsed) => parsed.ToUniversalTime(),
BsonType.DateTime => DateTime.SpecifyKind(lastValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(lastValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(lastValue.ToUniversalTime(), DateTimeKind.Utc),
_ => null,
};
}
@@ -45,12 +45,12 @@ internal sealed record DebianCursor(
return new DebianCursor(lastPublished, processed, pendingDocuments, pendingMappings, cache);
}
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(static id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(static id => id.ToString())),
};
if (LastPublished.HasValue)
@@ -60,15 +60,15 @@ internal sealed record DebianCursor(
if (ProcessedAdvisoryIds.Count > 0)
{
document["processedIds"] = new BsonArray(ProcessedAdvisoryIds);
document["processedIds"] = new DocumentArray(ProcessedAdvisoryIds);
}
if (FetchCache.Count > 0)
{
var cacheDoc = new BsonDocument();
var cacheDoc = new DocumentObject();
foreach (var (key, entry) in FetchCache)
{
cacheDoc[key] = entry.ToBsonDocument();
cacheDoc[key] = entry.ToDocumentObject();
}
document["fetchCache"] = cacheDoc;
@@ -114,9 +114,9 @@ internal sealed record DebianCursor(
return FetchCache.TryGetValue(key, out entry!);
}
private static IReadOnlyCollection<string> ReadStringArray(BsonDocument document, string field)
private static IReadOnlyCollection<string> ReadStringArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyIds;
}
@@ -124,7 +124,7 @@ internal sealed record DebianCursor(
var list = new List<string>(array.Count);
foreach (var element in array)
{
if (element.BsonType == BsonType.String)
if (element.DocumentType == DocumentType.String)
{
var str = element.AsString.Trim();
if (!string.IsNullOrEmpty(str))
@@ -137,9 +137,9 @@ internal sealed record DebianCursor(
return list;
}
private static IReadOnlyCollection<Guid> ReadGuidArray(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidArray(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}
@@ -156,9 +156,9 @@ internal sealed record DebianCursor(
return list;
}
private static IReadOnlyDictionary<string, DebianFetchCacheEntry> ReadCache(BsonDocument document)
private static IReadOnlyDictionary<string, DebianFetchCacheEntry> ReadCache(DocumentObject document)
{
if (!document.TryGetValue("fetchCache", out var value) || value is not BsonDocument cacheDocument || cacheDocument.ElementCount == 0)
if (!document.TryGetValue("fetchCache", out var value) || value is not DocumentObject cacheDocument || cacheDocument.ElementCount == 0)
{
return EmptyCache;
}
@@ -166,7 +166,7 @@ internal sealed record DebianCursor(
var cache = new Dictionary<string, DebianFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
foreach (var element in cacheDocument.Elements)
{
if (element.Value is BsonDocument entry)
if (element.Value is DocumentObject entry)
{
cache[element.Name] = DebianFetchCacheEntry.FromBson(entry);
}

View File

@@ -1,5 +1,5 @@
using System;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Distro.Debian.Internal;
@@ -10,7 +10,7 @@ internal sealed record DebianFetchCacheEntry(string? ETag, DateTimeOffset? LastM
public static DebianFetchCacheEntry FromDocument(StellaOps.Concelier.Storage.DocumentRecord document)
=> new(document.Etag, document.LastModified);
public static DebianFetchCacheEntry FromBson(BsonDocument document)
public static DebianFetchCacheEntry FromBson(DocumentObject document)
{
if (document is null || document.ElementCount == 0)
{
@@ -20,17 +20,17 @@ internal sealed record DebianFetchCacheEntry(string? ETag, DateTimeOffset? LastM
string? etag = null;
DateTimeOffset? lastModified = null;
if (document.TryGetValue("etag", out var etagValue) && etagValue.BsonType == BsonType.String)
if (document.TryGetValue("etag", out var etagValue) && etagValue.DocumentType == DocumentType.String)
{
etag = etagValue.AsString;
}
if (document.TryGetValue("lastModified", out var modifiedValue))
{
lastModified = modifiedValue.BsonType switch
lastModified = modifiedValue.DocumentType switch
{
BsonType.String when DateTimeOffset.TryParse(modifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
BsonType.DateTime => DateTime.SpecifyKind(modifiedValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(modifiedValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(modifiedValue.ToUniversalTime(), DateTimeKind.Utc),
_ => null,
};
}
@@ -38,9 +38,9 @@ internal sealed record DebianFetchCacheEntry(string? ETag, DateTimeOffset? LastM
return new DebianFetchCacheEntry(etag, lastModified);
}
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument();
var document = new DocumentObject();
if (!string.IsNullOrWhiteSpace(ETag))
{
document["etag"] = ETag;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Distro.RedHat.Internal;
@@ -19,7 +19,7 @@ internal sealed record RedHatCursor(
public static RedHatCursor Empty { get; } = new(null, EmptyStringList, EmptyGuidList, EmptyGuidList, EmptyCache);
public static RedHatCursor FromBsonDocument(BsonDocument? document)
public static RedHatCursor FromDocumentObject(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -40,22 +40,22 @@ internal sealed record RedHatCursor(
return new RedHatCursor(lastReleased, processed, pendingDocuments, pendingMappings, fetchCache);
}
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument();
var document = new DocumentObject();
if (LastReleasedOn.HasValue)
{
document["lastReleasedOn"] = LastReleasedOn.Value.UtcDateTime;
}
document["processedAdvisories"] = new BsonArray(ProcessedAdvisoryIds);
document["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString()));
document["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString()));
document["processedAdvisories"] = new DocumentArray(ProcessedAdvisoryIds);
document["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString()));
document["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString()));
var cacheArray = new BsonArray();
var cacheArray = new DocumentArray();
foreach (var (key, metadata) in FetchCache)
{
var cacheDoc = new BsonDocument
var cacheDoc = new DocumentObject
{
["uri"] = key
};
@@ -167,9 +167,9 @@ internal sealed record RedHatCursor(
return null;
}
private static IReadOnlyCollection<string> ReadStringSet(BsonDocument document, string field)
private static IReadOnlyCollection<string> ReadStringSet(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyStringList;
}
@@ -177,7 +177,7 @@ internal sealed record RedHatCursor(
var results = new List<string>(array.Count);
foreach (var element in array)
{
if (element.BsonType == BsonType.String)
if (element.DocumentType == DocumentType.String)
{
var str = element.AsString.Trim();
if (!string.IsNullOrWhiteSpace(str))
@@ -190,9 +190,9 @@ internal sealed record RedHatCursor(
return results;
}
private static IReadOnlyCollection<Guid> ReadGuidSet(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidSet(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}
@@ -200,7 +200,7 @@ internal sealed record RedHatCursor(
var results = new List<Guid>(array.Count);
foreach (var element in array)
{
if (element.BsonType == BsonType.String && Guid.TryParse(element.AsString, out var guid))
if (element.DocumentType == DocumentType.String && Guid.TryParse(element.AsString, out var guid))
{
results.Add(guid);
}
@@ -209,23 +209,23 @@ internal sealed record RedHatCursor(
return results;
}
private static IReadOnlyDictionary<string, RedHatCachedFetchMetadata> ReadFetchCache(BsonDocument document)
private static IReadOnlyDictionary<string, RedHatCachedFetchMetadata> ReadFetchCache(DocumentObject document)
{
if (!document.TryGetValue("fetchCache", out var value) || value is not BsonArray array || array.Count == 0)
if (!document.TryGetValue("fetchCache", out var value) || value is not DocumentArray array || array.Count == 0)
{
return EmptyCache;
}
var results = new Dictionary<string, RedHatCachedFetchMetadata>(StringComparer.OrdinalIgnoreCase);
foreach (var element in array.OfType<BsonDocument>())
foreach (var element in array.OfType<DocumentObject>())
{
if (!element.TryGetValue("uri", out var uriValue) || uriValue.BsonType != BsonType.String)
if (!element.TryGetValue("uri", out var uriValue) || uriValue.DocumentType != DocumentType.String)
{
continue;
}
var uri = uriValue.AsString;
var etag = element.TryGetValue("etag", out var etagValue) && etagValue.BsonType == BsonType.String
var etag = element.TryGetValue("etag", out var etagValue) && etagValue.DocumentType == DocumentType.String
? etagValue.AsString
: null;
DateTimeOffset? lastModified = null;
@@ -240,12 +240,12 @@ internal sealed record RedHatCursor(
return results;
}
private static DateTimeOffset? ReadDateTimeOffset(BsonValue value)
private static DateTimeOffset? ReadDateTimeOffset(DocumentValue value)
{
return value.BsonType switch
return value.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}

View File

@@ -5,8 +5,8 @@ using System.Linq;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Bson.IO;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Documents.IO;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
@@ -312,7 +312,7 @@ public sealed class RedHatConnector : IFeedConnector
var rawBytes = await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
using var jsonDocument = JsonDocument.Parse(rawBytes);
var sanitized = JsonSerializer.Serialize(jsonDocument.RootElement);
var payload = BsonDocument.Parse(sanitized);
var payload = DocumentObject.Parse(sanitized);
var dtoRecord = new DtoRecord(
Guid.NewGuid(),
@@ -402,13 +402,13 @@ public sealed class RedHatConnector : IFeedConnector
private async Task<RedHatCursor> GetCursorAsync(CancellationToken cancellationToken)
{
var record = await _stateRepository.TryGetAsync(SourceName, cancellationToken).ConfigureAwait(false);
return RedHatCursor.FromBsonDocument(record?.Cursor);
return RedHatCursor.FromDocumentObject(record?.Cursor);
}
private async Task UpdateCursorAsync(RedHatCursor cursor, CancellationToken cancellationToken)
{
var completedAt = _timeProvider.GetUtcNow();
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToBsonDocument(), completedAt, cancellationToken).ConfigureAwait(false);
await _stateRepository.UpdateCursorAsync(SourceName, cursor.ToDocumentObject(), completedAt, cancellationToken).ConfigureAwait(false);
}
private Uri BuildSummaryUri(DateTimeOffset after, int page)

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StellaOps.Concelier.Bson;
using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Distro.Suse.Internal;
@@ -19,7 +19,7 @@ internal sealed record SuseCursor(
public static SuseCursor Empty { get; } = new(null, EmptyStringList, EmptyGuidList, EmptyGuidList, EmptyCache);
public static SuseCursor FromBson(BsonDocument? document)
public static SuseCursor FromBson(DocumentObject? document)
{
if (document is null || document.ElementCount == 0)
{
@@ -29,10 +29,10 @@ internal sealed record SuseCursor(
DateTimeOffset? lastModified = null;
if (document.TryGetValue("lastModified", out var lastValue))
{
lastModified = lastValue.BsonType switch
lastModified = lastValue.DocumentType switch
{
BsonType.DateTime => DateTime.SpecifyKind(lastValue.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(lastValue.AsString, out var parsed) => parsed.ToUniversalTime(),
DocumentType.DateTime => DateTime.SpecifyKind(lastValue.ToUniversalTime(), DateTimeKind.Utc),
DocumentType.String when DateTimeOffset.TryParse(lastValue.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null,
};
}
@@ -45,12 +45,12 @@ internal sealed record SuseCursor(
return new SuseCursor(lastModified, processed, pendingDocs, pendingMappings, cache);
}
public BsonDocument ToBsonDocument()
public DocumentObject ToDocumentObject()
{
var document = new BsonDocument
var document = new DocumentObject
{
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(static id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(static id => id.ToString())),
["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(static id => id.ToString())),
["pendingMappings"] = new DocumentArray(PendingMappings.Select(static id => id.ToString())),
};
if (LastModified.HasValue)
@@ -60,15 +60,15 @@ internal sealed record SuseCursor(
if (ProcessedIds.Count > 0)
{
document["processedIds"] = new BsonArray(ProcessedIds);
document["processedIds"] = new DocumentArray(ProcessedIds);
}
if (FetchCache.Count > 0)
{
var cacheDocument = new BsonDocument();
var cacheDocument = new DocumentObject();
foreach (var (key, entry) in FetchCache)
{
cacheDocument[key] = entry.ToBsonDocument();
cacheDocument[key] = entry.ToDocumentObject();
}
document["fetchCache"] = cacheDocument;
@@ -114,9 +114,9 @@ internal sealed record SuseCursor(
return FetchCache.TryGetValue(key, out entry!);
}
private static IReadOnlyCollection<string> ReadStringSet(BsonDocument document, string field)
private static IReadOnlyCollection<string> ReadStringSet(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyStringList;
}
@@ -124,7 +124,7 @@ internal sealed record SuseCursor(
var list = new List<string>(array.Count);
foreach (var element in array)
{
if (element.BsonType == BsonType.String)
if (element.DocumentType == DocumentType.String)
{
var str = element.AsString.Trim();
if (!string.IsNullOrWhiteSpace(str))
@@ -137,9 +137,9 @@ internal sealed record SuseCursor(
return list;
}
private static IReadOnlyCollection<Guid> ReadGuidSet(BsonDocument document, string field)
private static IReadOnlyCollection<Guid> ReadGuidSet(DocumentObject document, string field)
{
if (!document.TryGetValue(field, out var value) || value is not BsonArray array)
if (!document.TryGetValue(field, out var value) || value is not DocumentArray array)
{
return EmptyGuidList;
}
@@ -156,9 +156,9 @@ internal sealed record SuseCursor(
return list;
}
private static IReadOnlyDictionary<string, SuseFetchCacheEntry> ReadCache(BsonDocument document)
private static IReadOnlyDictionary<string, SuseFetchCacheEntry> ReadCache(DocumentObject document)
{
if (!document.TryGetValue("fetchCache", out var value) || value is not BsonDocument cacheDocument || cacheDocument.ElementCount == 0)
if (!document.TryGetValue("fetchCache", out var value) || value is not DocumentObject cacheDocument || cacheDocument.ElementCount == 0)
{
return EmptyCache;
}
@@ -166,7 +166,7 @@ internal sealed record SuseCursor(
var cache = new Dictionary<string, SuseFetchCacheEntry>(StringComparer.OrdinalIgnoreCase);
foreach (var element in cacheDocument.Elements)
{
if (element.Value is BsonDocument entry)
if (element.Value is DocumentObject entry)
{
cache[element.Name] = SuseFetchCacheEntry.FromBson(entry);
}

Some files were not shown because too many files have changed in this diff Show More