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**. * **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). * **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. * **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. * **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. * **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`. * **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. | | **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. | | **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. | | **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. | | **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) ### 1.2 Thirdparty (selfhosted)

View File

@@ -6,7 +6,7 @@
- **Working directory:** `src/Web/StellaOps.Web` - **Working directory:** `src/Web/StellaOps.Web`
## Dependencies & Concurrency ## 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. - 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. - Parallel tracks: Can run alongside UI II/III for shared component work.
- Blockers to flag: VEX decision API schema finalization, Attestation viewer predicates. - Blockers to flag: VEX decision API schema finalization, Attestation viewer predicates.
@@ -18,59 +18,58 @@
- `docs/modules/ui/architecture.md` - `docs/modules/ui/architecture.md`
- `docs/modules/vuln-explorer/architecture.md` - `docs/modules/vuln-explorer/architecture.md`
- `docs/modules/vex-lens/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/archived/27-Nov-2025-superseded/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/27-Nov-2025 - Explainability Layer for Vulnerability Verdicts.md`
- `docs/schemas/vex-decision.schema.json` - `docs/schemas/vex-decision.schema.json`
- `docs/schemas/audit-bundle-index.schema.json` - `docs/schemas/audit-bundle-index.schema.json`
## Delivery Tracker ## Delivery Tracker
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | # | 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. | | 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 | 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). | | 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 | 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. | | 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 | 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. | | 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 | 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). | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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). | | 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 | 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. | | 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 | 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. | | 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 | TODO | - | API Guild (src/VulnExplorer) | Implement POST /v1/vex-decisions endpoint with VexDecisionDto request/response per schema, validation, attestation generation trigger. | | 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 | TODO | API-VEX-06-001 | API Guild (src/VulnExplorer) | Implement PATCH /v1/vex-decisions/{id} for updating existing decisions with supersedes tracking. | | 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 | TODO | API-VEX-06-002 | API Guild (src/VulnExplorer) | Implement GET /v1/vex-decisions with filters for vulnerabilityId, subject, status, scope, validFor. | | 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 | TODO | - | API Guild (src/ExportCenter) | Implement POST /v1/audit-bundles endpoint with bundle creation, index generation, ZIP/OCI artifact production. | | 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 | TODO | API-AUDIT-07-001 | API Guild (src/ExportCenter) | Implement GET /v1/audit-bundles/{bundleId} for bundle download with integrity verification. | | 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 | TODO | - | Platform Guild | Create docs/schemas/vex-decision.schema.json with JSON Schema 2020-12 definition per advisory. | | 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 | TODO | SCHEMA-08-001 | Platform Guild | Create docs/schemas/attestation-vuln-scan.schema.json for vulnerability scan attestation predicate. | | 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 | TODO | SCHEMA-08-002 | Platform Guild | Create docs/schemas/audit-bundle-index.schema.json for audit bundle manifest structure. | | 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 | TODO | SCHEMA-08-001 | API Guild | Create VexDecisionDto, SubjectRefDto, EvidenceRefDto, VexScopeDto, ValidForDto C# DTOs per advisory. | | 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 | TODO | SCHEMA-08-002 | API Guild | Create VulnScanAttestationDto, AttestationSubjectDto, VulnScanPredicateDto 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 | TODO | SCHEMA-08-003 | API Guild | Create AuditBundleIndexDto, BundleArtifactDto, BundleVexDecisionEntryDto 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 | 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. | | 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 Coordination
- **Wave A (Schemas & DTOs):** SCHEMA-08-*, DTO-09-*, TS-10-* - Foundation work - **Wave A (Schemas & DTOs):** SCHEMA-08-*, DTO-09-*, TS-10-* - Foundation work
@@ -80,7 +79,7 @@
## Wave Detail Snapshots ## Wave Detail Snapshots
### Wave A - Schemas & Types ### Wave A - Schemas & Types
- Duration: 2-3 days - 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 - Exit criteria: Schemas validate, DTOs compile, TS interfaces pass type checks
### Wave B - Backend APIs ### Wave B - Backend APIs
@@ -112,7 +111,8 @@
| 2 | Confirm attestation predicate types with Attestor team | API Guild | 2025-12-03 | TODO | | 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 | | 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 | | 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 ## Decisions & Risks
| Risk | Impact | Mitigation / Next Step | | Risk | Impact | Mitigation / Next Step |
@@ -121,20 +121,22 @@
| Attestation service not ready | UI-ATT-* tasks blocked | Mock attestation data; feature flag attestation views | | 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 | | 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 | | 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 | | Advisory doc sync lag | Docs drift from UX/API decisions | DOC-11-* DONE; re-review docs when schemas/APIs finalize |
| 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 | | 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 | | 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 ## Execution Log
| Date (UTC) | Update | Owner | | 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 | 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-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 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 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-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-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* *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 ## Topic & Scope
- Window: 2025-11-11 -> 2025-11-22 (UTC); finish the provable reachability pipeline so Sprint 0402 can focus on polish. - 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. - 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. - 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 ## 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. - 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 Tracker
| # | Action | Owner | Due (UTC) | Status | Notes | | # | 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. | | 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. | | 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. | | 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 | Open | Assess runtime ingestion/probe readiness and flip task statuses to DOING/BLOCKED accordingly. | | 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 ## Decisions & Risks
- File renamed to `SPRINT_0401_0001_0001_reachability_evidence_chain.md` and normalized to template on 2025-11-22; scope unchanged. - 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 | | 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-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 | 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 | 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 | | 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 ### T10.11: Package and Project Cleanup
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | # | 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 | | 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 | BLOCKED | MR-T10.11.1 | Infrastructure Guild | Remove MongoDB.Bson 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 | | 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 | | 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 | BLOCKED | MR-T10.11.4 | Infrastructure Guild | Final grep verification: zero MongoDB references | | 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 ## Wave Coordination
- Single-wave execution with module-by-module sequencing to keep the build green after each subtask. - 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 | 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 | 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-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. 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 --> <!-- TODO: Review for separate approval - added Decision Capsules as feature 0 -->
## 0. Decision Capsules Audit-Grade Evidence Bundles (2025-12) ## 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. - **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. - **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 ## 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. - **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_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. | | `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 ## Adapter responsibilities
- **JSON (`json:raw`, `json:policy`).** - **JSON (`json:raw`, `json:policy`).**
- Ensures canonical casing, timezone normalization, and linkset preservation. - 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. 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. - 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`. - 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. - 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. - 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. - 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/. - 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 ## Responsibilities
- Expose APIs (WebService) for scan orchestration, diffing, and artifact retrieval. - 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/entrypoint.md
- ./operations/secret-leak-detection.md - ./operations/secret-leak-detection.md
- ./operations/dsse-rekor-operator-guide.md - ./operations/dsse-rekor-operator-guide.md
- ./os-analyzers-evidence.md
- ./design/macos-analyzer.md - ./design/macos-analyzer.md
- ./design/windows-analyzer.md - ./design/windows-analyzer.md
- ../benchmarks/scanner/deep-dives/macos.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 ├─ runtime/ # Zastava posture, drift events, admission decisions
├─ policy/ # rules editor (YAML/Rego), exemptions, previews ├─ policy/ # rules editor (YAML/Rego), exemptions, previews
├─ vex/ # VEX explorer (claims, consensus, conflicts) ├─ vex/ # VEX explorer (claims, consensus, conflicts)
├─ triage/ # vulnerability triage (artifact-first), VEX decisions, audit bundles
├─ concelier/ # source health, export cursors, rebuild/export triggers ├─ concelier/ # source health, export cursors, rebuild/export triggers
├─ attest/ # attestation proofs, verification bundles, Rekor links ├─ attest/ # attestation proofs, verification bundles, Rekor links
├─ admin/ # tenants, roles, clients, quotas, licensing posture ├─ 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. * **Quotas**: per license plan, counters, throttle events.
* **Licensing posture**: last PoE introspection snapshot (redacted), release window. * **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 ## 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 ## 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 ### 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/{bundleId}` - Download bundle (ZIP or OCI)
- `GET /v1/audit-bundles` - List previously created bundles - `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 ### 8.6 Industry Pattern Alignment
The triage UX aligns with industry patterns from: 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.Fakes;
using StellaOps.Authority.Plugin.Ldap.Tests.TestHelpers; using StellaOps.Authority.Plugin.Ldap.Tests.TestHelpers;
using StellaOps.Authority.Plugins.Abstractions; using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Auth.Abstractions; using StellaOps.Auth.Abstractions;
using Xunit; 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.TestHelpers;
using StellaOps.Authority.Plugin.Ldap.Tests.Fakes; using StellaOps.Authority.Plugin.Ldap.Tests.Fakes;
using StellaOps.Authority.Plugins.Abstractions; using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using Xunit; using Xunit;
namespace StellaOps.Authority.Plugin.Ldap.Tests.Credentials; namespace StellaOps.Authority.Plugin.Ldap.Tests.Credentials;

View File

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

View File

@@ -5,12 +5,12 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MongoDB.Driver; using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Plugin.Ldap.Connections; using StellaOps.Authority.Plugin.Ldap.Connections;
using StellaOps.Authority.Plugin.Ldap.Security; using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Plugins.Abstractions; using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Auth.Abstractions; using StellaOps.Auth.Abstractions;
namespace StellaOps.Authority.Plugin.Ldap.ClientProvisioning; 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.Connections;
using StellaOps.Authority.Plugin.Ldap.Monitoring; using StellaOps.Authority.Plugin.Ldap.Monitoring;
using StellaOps.Authority.Plugin.Ldap.Security; using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Cryptography.Audit; using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Plugin.Ldap.Credentials; 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.Credentials;
using StellaOps.Authority.Plugin.Ldap.Monitoring; using StellaOps.Authority.Plugin.Ldap.Monitoring;
using StellaOps.Authority.Plugin.Ldap.Security; using StellaOps.Authority.Plugin.Ldap.Security;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Plugin.Ldap; namespace StellaOps.Authority.Plugin.Ldap;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,15 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver; using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Storage.Mongo.Initialization; using StellaOps.Authority.Storage.InMemory.Initialization;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
namespace StellaOps.Authority.Storage.Mongo.Extensions; namespace StellaOps.Authority.Storage.Mongo.Extensions;
/// <summary> /// <summary>
/// Compatibility shim storage options. In PostgreSQL mode, these are largely unused. /// Compatibility shim storage options. In PostgreSQL mode, these are largely unused.
/// </summary> /// </summary>
public sealed class AuthorityMongoStorageOptions public sealed class AuthorityStorageOptions
{ {
public string ConnectionString { get; set; } = string.Empty; public string ConnectionString { get; set; } = string.Empty;
public string DatabaseName { get; set; } = "authority"; public string DatabaseName { get; set; } = "authority";
@@ -28,9 +28,9 @@ public static class ServiceCollectionExtensions
/// </summary> /// </summary>
public static IServiceCollection AddAuthorityMongoStorage( public static IServiceCollection AddAuthorityMongoStorage(
this IServiceCollection services, this IServiceCollection services,
Action<AuthorityMongoStorageOptions> configureOptions) Action<AuthorityStorageOptions> configureOptions)
{ {
var options = new AuthorityMongoStorageOptions(); var options = new AuthorityStorageOptions();
configureOptions(options); configureOptions(options);
services.AddSingleton(options); services.AddSingleton(options);
@@ -38,19 +38,19 @@ public static class ServiceCollectionExtensions
return services; 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) // Register the initializer (no-op for Postgres mode)
services.AddSingleton<AuthorityMongoInitializer>(); services.AddSingleton<AuthorityStorageInitializer>();
// Register null session accessor // Register null session accessor
services.AddSingleton<IAuthorityMongoSessionAccessor, NullAuthorityMongoSessionAccessor>(); services.AddSingleton<IAuthoritySessionAccessor, NullAuthoritySessionAccessor>();
// Register in-memory MongoDB shims for compatibility // Register in-memory shims for compatibility
var inMemoryClient = new InMemoryMongoClient(); var inMemoryClient = new InMemoryClient();
var inMemoryDatabase = inMemoryClient.GetDatabase(options.DatabaseName); var inMemoryDatabase = inMemoryClient.GetDatabase(options.DatabaseName);
services.AddSingleton<IMongoClient>(inMemoryClient); services.AddSingleton<IClient>(inMemoryClient);
services.AddSingleton<IMongoDatabase>(inMemoryDatabase); services.AddSingleton<IDatabase>(inMemoryDatabase);
// Register in-memory store implementations // Register in-memory store implementations
// These should be replaced by Postgres-backed implementations over time // 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> /// <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. /// The actual initialization is handled by PostgreSQL migrations.
/// </summary> /// </summary>
public sealed class AuthorityMongoInitializer public sealed class AuthorityStorageInitializer
{ {
/// <summary> /// <summary>
/// Initializes the database. In PostgreSQL mode, this is a no-op as migrations handle setup. /// 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> /// <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> /// </summary>
public interface IAuthorityMongoSessionAccessor public interface IAuthoritySessionAccessor
{ {
IClientSessionHandle? CurrentSession { get; } IClientSessionHandle? CurrentSession { get; }
ValueTask<IClientSessionHandle?> GetSessionAsync(CancellationToken cancellationToken); ValueTask<IClientSessionHandle?> GetSessionAsync(CancellationToken cancellationToken);
@@ -19,7 +19,7 @@ public interface IAuthorityMongoSessionAccessor
/// <summary> /// <summary>
/// In-memory implementation that always returns null session. /// In-memory implementation that always returns null session.
/// </summary> /// </summary>
public sealed class NullAuthorityMongoSessionAccessor : IAuthorityMongoSessionAccessor public sealed class NullAuthoritySessionAccessor : IAuthoritySessionAccessor
{ {
public IClientSessionHandle? CurrentSession => null; public IClientSessionHandle? CurrentSession => null;

View File

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

View File

@@ -1,9 +1,9 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
namespace StellaOps.Authority.Storage.Mongo.Stores; namespace StellaOps.Authority.Storage.InMemory.Stores;
/// <summary> /// <summary>
/// In-memory implementation of bootstrap invite store for development/testing. /// 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.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using StellaOps.Auth.Abstractions; using StellaOps.Auth.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Tests.Infrastructure; using StellaOps.Authority.Tests.Infrastructure;
using StellaOps.Configuration; using StellaOps.Configuration;
using Xunit; using Xunit;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,9 +30,9 @@ using StellaOps.Authority.Airgap;
using StellaOps.Authority.OpenIddict; using StellaOps.Authority.OpenIddict;
using StellaOps.Authority.OpenIddict.Handlers; using StellaOps.Authority.OpenIddict.Handlers;
using StellaOps.Authority.Plugins.Abstractions; using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.RateLimiting; using StellaOps.Authority.RateLimiting;
using StellaOps.Cryptography.Audit; using StellaOps.Cryptography.Audit;
using Xunit; 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; public IClientSessionHandle? CurrentSession => null;

View File

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

View File

@@ -5,9 +5,9 @@ using Microsoft.Extensions.Time.Testing;
using OpenIddict.Abstractions; using OpenIddict.Abstractions;
using OpenIddict.Server; using OpenIddict.Server;
using StellaOps.Authority.OpenIddict.Handlers; using StellaOps.Authority.OpenIddict.Handlers;
using StellaOps.Authority.Storage.Mongo.Documents; using StellaOps.Authority.Storage.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using Xunit; using Xunit;
namespace StellaOps.Authority.Tests.OpenIddict; 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 issuedAt = new DateTimeOffset(2025, 10, 10, 12, 0, 0, TimeSpan.Zero);
var clock = new FakeTimeProvider(issuedAt); var clock = new FakeTimeProvider(issuedAt);
var tokenStore = new InMemoryTokenStore(); 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"); var identity = new ClaimsIdentity(authenticationType: "test");
identity.SetClaim(OpenIddictConstants.Claims.Subject, "subject-1"); identity.SetClaim(OpenIddictConstants.Claims.Subject, "subject-1");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,7 @@
<PackageReference Include="YamlDotNet" Version="13.7.1" /> <PackageReference Include="YamlDotNet" Version="13.7.1" />
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" /> <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.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="..\..\__Libraries\StellaOps.Authority.Storage.Postgres\StellaOps.Authority.Storage.Postgres.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" /> <ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.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.InMemory.Documents;
using StellaOps.Authority.Storage.Mongo.Sessions; using StellaOps.Authority.Storage.InMemory.Sessions;
using StellaOps.Authority.Storage.Mongo.Stores; using StellaOps.Authority.Storage.InMemory.Stores;
using StellaOps.Authority.Storage.Postgres.Models; using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories; using StellaOps.Authority.Storage.Postgres.Repositories;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,8 @@ using System.Xml.Linq;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson; using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Bson.IO; using StellaOps.Concelier.Documents.IO;
using StellaOps.Concelier.Connector.Acsc.Configuration; using StellaOps.Concelier.Connector.Acsc.Configuration;
using StellaOps.Concelier.Connector.Acsc.Internal; using StellaOps.Concelier.Connector.Acsc.Internal;
using StellaOps.Concelier.Connector.Common.Fetch; 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 dto = AcscFeedParser.Parse(rawBytes, metadata.FeedSlug, parsedAt, _htmlSanitizer);
var json = JsonSerializer.Serialize(dto, SerializerOptions); 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 existingDto = await _dtoStore.FindByDocumentIdAsync(document.Id, cancellationToken).ConfigureAwait(false);
var dtoRecord = existingDto is null var dtoRecord = existingDto is null
@@ -678,7 +678,7 @@ public sealed class AcscConnector : IFeedConnector
private Task UpdateCursorAsync(AcscCursor cursor, CancellationToken cancellationToken) private Task UpdateCursorAsync(AcscCursor cursor, CancellationToken cancellationToken)
{ {
var document = cursor.ToBsonDocument(); var document = cursor.ToDocumentObject();
var completedAt = _timeProvider.GetUtcNow(); var completedAt = _timeProvider.GetUtcNow();
return _stateRepository.UpdateCursorAsync(SourceName, document, completedAt, cancellationToken); 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; namespace StellaOps.Concelier.Connector.Acsc.Internal;
@@ -48,16 +48,16 @@ internal sealed record AcscCursor(
return this with { LastPublishedByFeed = snapshot }; return this with { LastPublishedByFeed = snapshot };
} }
public BsonDocument ToBsonDocument() public DocumentObject ToDocumentObject()
{ {
var document = new BsonDocument var document = new DocumentObject
{ {
["preferredEndpoint"] = PreferredEndpoint.ToString(), ["preferredEndpoint"] = PreferredEndpoint.ToString(),
["pendingDocuments"] = new BsonArray(PendingDocuments.Select(id => id.ToString())), ["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.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) foreach (var kvp in LastPublishedByFeed)
{ {
if (kvp.Value.HasValue) if (kvp.Value.HasValue)
@@ -70,7 +70,7 @@ internal sealed record AcscCursor(
return document; return document;
} }
public static AcscCursor FromBson(BsonDocument? document) public static AcscCursor FromBson(DocumentObject? document)
{ {
if (document is null || document.ElementCount == 0) if (document is null || document.ElementCount == 0)
{ {
@@ -82,7 +82,7 @@ internal sealed record AcscCursor(
: AcscEndpointPreference.Auto; : AcscEndpointPreference.Auto;
var feeds = new Dictionary<string, DateTimeOffset?>(StringComparer.OrdinalIgnoreCase); 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) foreach (var element in feedsDocument.Elements)
{ {
@@ -100,9 +100,9 @@ internal sealed record AcscCursor(
pendingMappings); 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; return EmptyGuidList;
} }
@@ -119,12 +119,12 @@ internal sealed record AcscCursor(
return list; 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), DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(), DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null, _ => null,
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson; using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Connector.CertFr.Configuration; using StellaOps.Concelier.Connector.CertFr.Configuration;
using StellaOps.Concelier.Connector.CertFr.Internal; using StellaOps.Concelier.Connector.CertFr.Internal;
using StellaOps.Concelier.Connector.Common; using StellaOps.Concelier.Connector.Common;
@@ -236,7 +236,7 @@ public sealed class CertFrConnector : IFeedConnector
} }
var json = JsonSerializer.Serialize(dto, SerializerOptions); var json = JsonSerializer.Serialize(dto, SerializerOptions);
var payload = BsonDocument.Parse(json); var payload = DocumentObject.Parse(json);
var validatedAt = _timeProvider.GetUtcNow(); var validatedAt = _timeProvider.GetUtcNow();
var existingDto = await _dtoStore.FindByDocumentIdAsync(document.Id, cancellationToken).ConfigureAwait(false); 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) private async Task UpdateCursorAsync(CertFrCursor cursor, CancellationToken cancellationToken)
{ {
var completedAt = _timeProvider.GetUtcNow(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using StellaOps.Concelier.Bson; using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.CertFr.Internal; 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 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())), ["pendingDocuments"] = new DocumentArray(PendingDocuments.Select(id => id.ToString())),
["pendingMappings"] = new BsonArray(PendingMappings.Select(id => id.ToString())), ["pendingMappings"] = new DocumentArray(PendingMappings.Select(id => id.ToString())),
}; };
if (LastPublished.HasValue) if (LastPublished.HasValue)
@@ -28,7 +28,7 @@ internal sealed record CertFrCursor(
return document; return document;
} }
public static CertFrCursor FromBson(BsonDocument? document) public static CertFrCursor FromBson(DocumentObject? document)
{ {
if (document is null || document.ElementCount == 0) if (document is null || document.ElementCount == 0)
{ {
@@ -54,17 +54,17 @@ internal sealed record CertFrCursor(
public CertFrCursor WithPendingMappings(IEnumerable<Guid> ids) public CertFrCursor WithPendingMappings(IEnumerable<Guid> ids)
=> this with { PendingMappings = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() }; => this with { PendingMappings = ids?.Distinct().ToArray() ?? Array.Empty<Guid>() };
private static DateTimeOffset? ParseDate(BsonValue value) private static DateTimeOffset? ParseDate(DocumentValue value)
=> value.BsonType switch => value.DocumentType switch
{ {
BsonType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc), DocumentType.DateTime => DateTime.SpecifyKind(value.ToUniversalTime(), DateTimeKind.Utc),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(), DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null, _ => 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>(); return Array.Empty<Guid>();
} }

View File

@@ -6,7 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using StellaOps.Concelier.Bson; using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models; using StellaOps.Concelier.Models;
using StellaOps.Concelier.Connector.CertIn.Configuration; using StellaOps.Concelier.Connector.CertIn.Configuration;
using StellaOps.Concelier.Connector.CertIn.Internal; using StellaOps.Concelier.Connector.CertIn.Internal;
@@ -226,7 +226,7 @@ public sealed class CertInConnector : IFeedConnector
} }
var dto = CertInDetailParser.Parse(listing, rawBytes); 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()); var dtoRecord = new DtoRecord(Guid.NewGuid(), document.Id, SourceName, "certin.v1", payload, _timeProvider.GetUtcNow());
await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false); await _dtoStore.UpsertAsync(dtoRecord, cancellationToken).ConfigureAwait(false);
@@ -271,9 +271,9 @@ public sealed class CertInConnector : IFeedConnector
continue; 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; CertInAdvisoryDto dto;
@@ -423,7 +423,7 @@ public sealed class CertInConnector : IFeedConnector
private Task UpdateCursorAsync(CertInCursor cursor, CancellationToken cancellationToken) 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) private static bool TryDeserializeListing(IReadOnlyDictionary<string, string>? metadata, out CertInListingItem listing)

View File

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

View File

@@ -1,4 +1,4 @@
using StellaOps.Concelier.Bson; using StellaOps.Concelier.Documents;
namespace StellaOps.Concelier.Connector.Common.Cursors; namespace StellaOps.Concelier.Connector.Common.Cursors;
@@ -14,14 +14,14 @@ public sealed record TimeWindowCursorState(DateTimeOffset? LastWindowStart, Date
return new TimeWindowCursorState(window.Start, window.End); 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); WriteTo(document, startField, endField);
return document; 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); ArgumentNullException.ThrowIfNull(document);
ArgumentException.ThrowIfNullOrEmpty(startField); 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) if (document is null)
{ {
@@ -64,12 +64,12 @@ public sealed record TimeWindowCursorState(DateTimeOffset? LastWindowStart, Date
return new TimeWindowCursorState(start, end); 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), DocumentType.DateTime => new DateTimeOffset(value.ToUniversalTime(), TimeSpan.Zero),
BsonType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(), DocumentType.String when DateTimeOffset.TryParse(value.AsString, out var parsed) => parsed.ToUniversalTime(),
_ => null, _ => null,
}; };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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