diff --git a/.gitea/workflows/feedser-ci.yml b/.gitea/workflows/_deprecated-feedser-ci.yml.disabled similarity index 100% rename from .gitea/workflows/feedser-ci.yml rename to .gitea/workflows/_deprecated-feedser-ci.yml.disabled diff --git a/.gitea/workflows/feedser-tests.yml b/.gitea/workflows/_deprecated-feedser-tests.yml.disabled similarity index 100% rename from .gitea/workflows/feedser-tests.yml rename to .gitea/workflows/_deprecated-feedser-tests.yml.disabled diff --git a/.gitea/workflows/build-test-deploy.yml b/.gitea/workflows/build-test-deploy.yml new file mode 100644 index 00000000..4beeae2e --- /dev/null +++ b/.gitea/workflows/build-test-deploy.yml @@ -0,0 +1,297 @@ +# .gitea/workflows/build-test-deploy.yml +# Unified CI/CD workflow for git.stella-ops.org (Feedser monorepo) + +name: Build Test Deploy + +on: + push: + branches: [ main ] + paths: + - 'src/**' + - 'docs/**' + - 'scripts/**' + - 'Directory.Build.props' + - 'Directory.Build.targets' + - 'global.json' + - '.gitea/workflows/**' + pull_request: + branches: [ main, develop ] + paths: + - 'src/**' + - 'docs/**' + - 'scripts/**' + - '.gitea/workflows/**' + workflow_dispatch: + inputs: + force_deploy: + description: 'Ignore branch checks and run the deploy stage' + required: false + default: 'false' + type: boolean + +env: + DOTNET_VERSION: '10.0.100-rc.1.25451.107' + BUILD_CONFIGURATION: Release + CI_CACHE_ROOT: /data/.cache/stella-ops/feedser + RUNNER_TOOL_CACHE: /toolcache + +jobs: + build-test: + runs-on: ubuntu-22.04 + environment: ${{ github.event_name == 'pull_request' && 'preview' || 'staging' }} + env: + PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/webservice + TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET ${{ env.DOTNET_VERSION }} + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + include-prerelease: true + + - name: Restore dependencies + run: dotnet restore src/StellaOps.Feedser.sln + + - name: Build solution (warnings as errors) + run: dotnet build src/StellaOps.Feedser.sln --configuration $BUILD_CONFIGURATION --no-restore -warnaserror + + - name: Run unit and integration tests + run: | + mkdir -p "$TEST_RESULTS_DIR" + dotnet test src/StellaOps.Feedser.sln \ + --configuration $BUILD_CONFIGURATION \ + --no-build \ + --logger "trx;LogFileName=stellaops-feedser-tests.trx" \ + --results-directory "$TEST_RESULTS_DIR" + + - name: Publish Feedser web service + run: | + mkdir -p "$PUBLISH_DIR" + dotnet publish src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj \ + --configuration $BUILD_CONFIGURATION \ + --no-build \ + --output "$PUBLISH_DIR" + + - name: Upload published artifacts + uses: actions/upload-artifact@v4 + with: + name: feedser-publish + path: ${{ env.PUBLISH_DIR }} + if-no-files-found: error + retention-days: 7 + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: feedser-test-results + path: ${{ env.TEST_RESULTS_DIR }} + if-no-files-found: ignore + retention-days: 7 + + docs: + runs-on: ubuntu-22.04 + env: + DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-site + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + python -m pip install markdown pygments + + - name: Render documentation bundle + run: | + python scripts/render_docs.py --source docs --output "$DOCS_OUTPUT_DIR" --clean + + - name: Upload documentation artifact + uses: actions/upload-artifact@v4 + with: + name: feedser-docs-site + path: ${{ env.DOCS_OUTPUT_DIR }} + if-no-files-found: error + retention-days: 7 + + deploy: + runs-on: ubuntu-22.04 + needs: [build-test, docs] + if: >- + needs.build-test.result == 'success' && + needs.docs.result == 'success' && + ( + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + github.event_name == 'workflow_dispatch' + ) + environment: staging + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + sparse-checkout: | + scripts + .gitea/workflows + sparse-checkout-cone-mode: true + + - name: Check if deployment should proceed + id: check-deploy + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ github.event.inputs.force_deploy }}" = "true" ]; then + echo "should-deploy=true" >> $GITHUB_OUTPUT + echo "✅ Manual deployment requested" + else + echo "should-deploy=false" >> $GITHUB_OUTPUT + echo "ℹ️ Manual dispatch without force_deploy=true — skipping" + fi + elif [ "${{ github.ref }}" = "refs/heads/main" ]; then + echo "should-deploy=true" >> $GITHUB_OUTPUT + echo "✅ Deploying latest main branch build" + else + echo "should-deploy=false" >> $GITHUB_OUTPUT + echo "ℹ️ Deployment restricted to main branch" + fi + + - name: Resolve deployment credentials + id: params + if: steps.check-deploy.outputs.should-deploy == 'true' + run: | + missing=() + + host="${{ secrets.STAGING_DEPLOYMENT_HOST }}" + if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi + + user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}" + if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi + + path="${{ secrets.STAGING_DEPLOYMENT_PATH }}" + if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi + + docs_path="${{ secrets.STAGING_DOCS_PATH }}" + if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi + + key="${{ secrets.STAGING_DEPLOYMENT_KEY }}" + if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi + + if [ ${#missing[@]} -gt 0 ]; then + echo "❌ Missing deployment configuration: ${missing[*]}" + exit 1 + fi + + key_file="$RUNNER_TEMP/staging_deploy_key" + printf '%s\n' "$key" > "$key_file" + chmod 600 "$key_file" + + echo "host=$host" >> $GITHUB_OUTPUT + echo "user=$user" >> $GITHUB_OUTPUT + echo "path=$path" >> $GITHUB_OUTPUT + echo "docs-path=$docs_path" >> $GITHUB_OUTPUT + echo "key-file=$key_file" >> $GITHUB_OUTPUT + + - name: Download service artifact + if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != '' + uses: actions/download-artifact@v4 + with: + name: feedser-publish + path: artifacts/service + + - name: Download documentation artifact + if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != '' + uses: actions/download-artifact@v4 + with: + name: feedser-docs-site + path: artifacts/docs + + - name: Install rsync + if: steps.check-deploy.outputs.should-deploy == 'true' + run: | + if command -v rsync >/dev/null 2>&1; then + exit 0 + fi + CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt" + mkdir -p "$CACHE_DIR" + KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)" + DEB_DIR="$CACHE_DIR/$KEY" + mkdir -p "$DEB_DIR" + if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then + apt-get update + apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb + else + apt-get update + apt-get download rsync libpopt0 + mv rsync*.deb libpopt0*.deb "$DEB_DIR"/ + dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y + fi + + - name: Deploy service bundle + if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs.path != '' + env: + HOST: ${{ steps.params.outputs.host }} + USER: ${{ steps.params.outputs.user }} + TARGET: ${{ steps.params.outputs.path }} + KEY_FILE: ${{ steps.params.outputs['key-file'] }} + run: | + SERVICE_DIR="artifacts/service/feedser-publish" + if [ ! -d "$SERVICE_DIR" ]; then + echo "❌ Service artifact directory missing ($SERVICE_DIR)" + exit 1 + fi + echo "🚀 Deploying Feedser web service to $HOST:$TARGET" + rsync -az --delete \ + -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \ + "$SERVICE_DIR"/ \ + "$USER@$HOST:$TARGET/" + + - name: Deploy documentation bundle + if: steps.check-deploy.outputs.should-deploy == 'true' && steps.params.outputs['docs-path'] != '' + env: + HOST: ${{ steps.params.outputs.host }} + USER: ${{ steps.params.outputs.user }} + DOCS_TARGET: ${{ steps.params.outputs['docs-path'] }} + KEY_FILE: ${{ steps.params.outputs['key-file'] }} + run: | + DOCS_DIR="artifacts/docs/feedser-docs-site" + if [ ! -d "$DOCS_DIR" ]; then + echo "❌ Documentation artifact directory missing ($DOCS_DIR)" + exit 1 + fi + echo "📚 Deploying documentation bundle to $HOST:$DOCS_TARGET" + rsync -az --delete \ + -e "ssh -i $KEY_FILE -o StrictHostKeyChecking=no" \ + "$DOCS_DIR"/ \ + "$USER@$HOST:$DOCS_TARGET/" + + - name: Deployment summary + if: steps.check-deploy.outputs.should-deploy == 'true' + run: | + echo "✅ Deployment completed" + echo " Host: ${{ steps.params.outputs.host }}" + echo " Service path: ${{ steps.params.outputs.path || '(skipped)' }}" + echo " Docs path: ${{ steps.params.outputs['docs-path'] || '(skipped)' }}" + + - name: Deployment skipped summary + if: steps.check-deploy.outputs.should-deploy != 'true' + run: | + echo "ℹ️ Deployment stage skipped" + echo " Event: ${{ github.event_name }}" + echo " Ref: ${{ github.ref }}" diff --git a/.gitea/workflows/docs.yml b/.gitea/workflows/docs.yml index 153796a7..35572726 100755 --- a/.gitea/workflows/docs.yml +++ b/.gitea/workflows/docs.yml @@ -1,30 +1,70 @@ -name: Docs CI - -on: - pull_request: - paths: - - 'docs/**' - - '.github/workflows/docs.yml' - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install markdown linters - run: | - npm install -g markdown-link-check remark-cli remark-preset-lint-recommended - - - name: Link check - run: | - find docs -name '*.md' -print0 | xargs -0 -n1 markdown-link-check -q - - - name: Remark lint - run: | - remark docs -qf +# .gitea/workflows/docs.yml +# Documentation quality checks and preview artefacts + +name: Docs CI + +on: + push: + paths: + - 'docs/**' + - 'scripts/render_docs.py' + - '.gitea/workflows/docs.yml' + pull_request: + paths: + - 'docs/**' + - 'scripts/render_docs.py' + - '.gitea/workflows/docs.yml' + workflow_dispatch: {} + +env: + NODE_VERSION: '20' + PYTHON_VERSION: '3.11' + +jobs: + lint-and-preview: + runs-on: ubuntu-22.04 + env: + DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-preview + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Install markdown linters + run: | + npm install markdown-link-check remark-cli remark-preset-lint-recommended + + - name: Link check + run: | + find docs -name '*.md' -print0 | \ + xargs -0 -n1 -I{} npx markdown-link-check --quiet '{}' + + - name: Remark lint + run: | + npx remark docs -qf + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install documentation dependencies + run: | + python -m pip install --upgrade pip + python -m pip install markdown pygments + + - name: Render documentation preview bundle + run: | + python scripts/render_docs.py --source docs --output "$DOCS_OUTPUT_DIR" --clean + + - name: Upload documentation preview + if: always() + uses: actions/upload-artifact@v4 + with: + name: feedser-docs-preview + path: ${{ env.DOCS_OUTPUT_DIR }} + retention-days: 7 diff --git a/.gitea/workflows/promote.yml b/.gitea/workflows/promote.yml new file mode 100644 index 00000000..141dd228 --- /dev/null +++ b/.gitea/workflows/promote.yml @@ -0,0 +1,206 @@ +# .gitea/workflows/promote.yml +# Manual promotion workflow to copy staged artefacts to production + +name: Promote Feedser (Manual) + +on: + workflow_dispatch: + inputs: + include_docs: + description: 'Also promote the generated documentation bundle' + required: false + default: 'true' + type: boolean + tag: + description: 'Optional build identifier to record in the summary' + required: false + default: 'latest' + type: string + +jobs: + promote: + runs-on: ubuntu-22.04 + environment: production + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Resolve staging credentials + id: staging + run: | + missing=() + + host="${{ secrets.STAGING_DEPLOYMENT_HOST }}" + if [ -z "$host" ]; then host="${{ vars.STAGING_DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then missing+=("STAGING_DEPLOYMENT_HOST"); fi + + user="${{ secrets.STAGING_DEPLOYMENT_USERNAME }}" + if [ -z "$user" ]; then user="${{ vars.STAGING_DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then missing+=("STAGING_DEPLOYMENT_USERNAME"); fi + + path="${{ secrets.STAGING_DEPLOYMENT_PATH }}" + if [ -z "$path" ]; then path="${{ vars.STAGING_DEPLOYMENT_PATH }}"; fi + if [ -z "$path" ]; then missing+=("STAGING_DEPLOYMENT_PATH") + fi + + docs_path="${{ secrets.STAGING_DOCS_PATH }}" + if [ -z "$docs_path" ]; then docs_path="${{ vars.STAGING_DOCS_PATH }}"; fi + + key="${{ secrets.STAGING_DEPLOYMENT_KEY }}" + if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.STAGING_DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then missing+=("STAGING_DEPLOYMENT_KEY"); fi + + if [ ${#missing[@]} -gt 0 ]; then + echo "❌ Missing staging configuration: ${missing[*]}" + exit 1 + fi + + key_file="$RUNNER_TEMP/staging_key" + printf '%s\n' "$key" > "$key_file" + chmod 600 "$key_file" + + echo "host=$host" >> $GITHUB_OUTPUT + echo "user=$user" >> $GITHUB_OUTPUT + echo "path=$path" >> $GITHUB_OUTPUT + echo "docs-path=$docs_path" >> $GITHUB_OUTPUT + echo "key-file=$key_file" >> $GITHUB_OUTPUT + + - name: Resolve production credentials + id: production + run: | + missing=() + + host="${{ secrets.PRODUCTION_DEPLOYMENT_HOST }}" + if [ -z "$host" ]; then host="${{ vars.PRODUCTION_DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ secrets.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then host="${{ vars.DEPLOYMENT_HOST }}"; fi + if [ -z "$host" ]; then missing+=("PRODUCTION_DEPLOYMENT_HOST"); fi + + user="${{ secrets.PRODUCTION_DEPLOYMENT_USERNAME }}" + if [ -z "$user" ]; then user="${{ vars.PRODUCTION_DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ secrets.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then user="${{ vars.DEPLOYMENT_USERNAME }}"; fi + if [ -z "$user" ]; then missing+=("PRODUCTION_DEPLOYMENT_USERNAME"); fi + + path="${{ secrets.PRODUCTION_DEPLOYMENT_PATH }}" + if [ -z "$path" ]; then path="${{ vars.PRODUCTION_DEPLOYMENT_PATH }}"; fi + if [ -z "$path" ]; then missing+=("PRODUCTION_DEPLOYMENT_PATH") + fi + + docs_path="${{ secrets.PRODUCTION_DOCS_PATH }}" + if [ -z "$docs_path" ]; then docs_path="${{ vars.PRODUCTION_DOCS_PATH }}"; fi + + key="${{ secrets.PRODUCTION_DEPLOYMENT_KEY }}" + if [ -z "$key" ]; then key="${{ secrets.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.PRODUCTION_DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then key="${{ vars.DEPLOYMENT_KEY }}"; fi + if [ -z "$key" ]; then missing+=("PRODUCTION_DEPLOYMENT_KEY"); fi + + if [ ${#missing[@]} -gt 0 ]; then + echo "❌ Missing production configuration: ${missing[*]}" + exit 1 + fi + + key_file="$RUNNER_TEMP/production_key" + printf '%s\n' "$key" > "$key_file" + chmod 600 "$key_file" + + echo "host=$host" >> $GITHUB_OUTPUT + echo "user=$user" >> $GITHUB_OUTPUT + echo "path=$path" >> $GITHUB_OUTPUT + echo "docs-path=$docs_path" >> $GITHUB_OUTPUT + echo "key-file=$key_file" >> $GITHUB_OUTPUT + + - name: Install rsync + run: | + if command -v rsync >/dev/null 2>&1; then + exit 0 + fi + CACHE_DIR="${CI_CACHE_ROOT:-/tmp}/apt" + mkdir -p "$CACHE_DIR" + KEY="rsync-$(lsb_release -rs 2>/dev/null || echo unknown)" + DEB_DIR="$CACHE_DIR/$KEY" + mkdir -p "$DEB_DIR" + if ls "$DEB_DIR"/rsync*.deb >/dev/null 2>&1; then + apt-get update + apt-get install -y --no-install-recommends "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb + else + apt-get update + apt-get download rsync libpopt0 + mv rsync*.deb libpopt0*.deb "$DEB_DIR"/ + dpkg -i "$DEB_DIR"/libpopt0*.deb "$DEB_DIR"/rsync*.deb || apt-get install -f -y + fi + + - name: Fetch staging artefacts + id: fetch + run: | + staging_root="${{ runner.temp }}/staging" + mkdir -p "$staging_root/service" "$staging_root/docs" + + echo "📥 Copying service bundle from staging" + rsync -az --delete \ + -e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \ + "${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs.path }}/" \ + "$staging_root/service/" + + if [ "${{ github.event.inputs.include_docs }}" = "true" ] && [ -n "${{ steps.staging.outputs['docs-path'] }}" ]; then + echo "📥 Copying documentation bundle from staging" + rsync -az --delete \ + -e "ssh -i ${{ steps.staging.outputs['key-file'] }} -o StrictHostKeyChecking=no" \ + "${{ steps.staging.outputs.user }}@${{ steps.staging.outputs.host }}:${{ steps.staging.outputs['docs-path'] }}/" \ + "$staging_root/docs/" + else + echo "ℹ️ Documentation promotion skipped" + fi + + echo "service-dir=$staging_root/service" >> $GITHUB_OUTPUT + echo "docs-dir=$staging_root/docs" >> $GITHUB_OUTPUT + + - name: Backup production service content + run: | + ssh -o StrictHostKeyChecking=no -i "${{ steps.production.outputs['key-file'] }}" \ + "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}" \ + "set -e; TARGET='${{ steps.production.outputs.path }}'; \ + if [ -d \"$TARGET\" ]; then \ + parent=\$(dirname \"$TARGET\"); \ + base=\$(basename \"$TARGET\"); \ + backup=\"\$parent/\${base}.backup.\$(date +%Y%m%d_%H%M%S)\"; \ + mkdir -p \"\$backup\"; \ + rsync -a --delete \"$TARGET/\" \"\$backup/\"; \ + ls -dt \"\$parent/\${base}.backup.*\" 2>/dev/null | tail -n +6 | xargs rm -rf || true; \ + echo 'Backup created at ' \"\$backup\"; \ + else \ + echo 'Production service path missing; skipping backup'; \ + fi" + + - name: Publish service to production + run: | + rsync -az --delete \ + -e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \ + "${{ steps.fetch.outputs['service-dir'] }}/" \ + "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs.path }}/" + + - name: Promote documentation bundle + if: github.event.inputs.include_docs == 'true' && steps.production.outputs['docs-path'] != '' + run: | + rsync -az --delete \ + -e "ssh -i ${{ steps.production.outputs['key-file'] }} -o StrictHostKeyChecking=no" \ + "${{ steps.fetch.outputs['docs-dir'] }}/" \ + "${{ steps.production.outputs.user }}@${{ steps.production.outputs.host }}:${{ steps.production.outputs['docs-path'] }}/" + + - name: Promotion summary + run: | + echo "✅ Promotion completed" + echo " Tag: ${{ github.event.inputs.tag }}" + echo " Service: ${{ steps.staging.outputs.host }} → ${{ steps.production.outputs.host }}" + if [ "${{ github.event.inputs.include_docs }}" = "true" ]; then + echo " Docs: included" + else + echo " Docs: skipped" + fi diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..8764b86d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,125 @@ +# 1) What is StellaOps? + +**StellaOps** an open, sovereign, modular container-security toolkit built for high-speed, offline operation, released under AGPL-3.0-or-later. + +It follows an SBOM-first model—analyzing each container layer or ingesting existing CycloneDX/SPDX SBOMs, then enriching them with vulnerability, licence, secret-leak, and misconfiguration data to produce cryptographically signed reports. + +Vulnerability detection maps OS and language dependencies to sources such as NVD, GHSA, OSV, ENISA. +Secrets sweep flags exposed credentials or keys in files or environment variables. +Licence audit identifies potential conflicts, especially copyleft obligations. +Misconfiguration checks detect unsafe Dockerfile patterns (root user, latest tags, permissive modes). +Provenance features include in-toto/SLSA attestations signed with cosign for supply-chain trust. + +| Guiding principle | What it means for Feedser | +|-------------------|---------------------------| +| **SBOM-first ingest** | Prefer signed SBOMs or reproducible layer diffs before falling back to raw scraping; connectors treat source docs as provenance, never as mutable truth. | +| **Deterministic outputs** | Same inputs yield identical canonical advisories and exported JSON/Trivy DB artefacts; merge hashes and export manifests are reproducible across machines. | +| **Restart-time plug-ins only** | Connector/exporter plug-ins load at service start, keeping runtime sandboxing simple and avoiding hot-patch attack surface. | +| **Sovereign/offline-first** | No mandatory outbound calls beyond allow-listed advisories; Offline Kit bundles Mongo snapshots and exporter artefacts for air-gapped installs. | +| **Operational transparency** | Every stage logs structured events (fetch, parse, merge, export) with correlation IDs so parallel agents can debug without shared state. | + +Performance: warm scans < 5 s, cold scans < 30 s on a 4 vCPU runner. +Deployment: entirely SaaS-free, suitable for air-gapped or on-prem use through its Offline Kit. +Policy: anonymous users → 33 scans/day; verified → 333 /day; nearing 90 % quota triggers throttling but never full blocks. + +More documention is available ./docs/*.md files. Read `docs/README.md` to gather information about the available documentation. You could inquiry specific documents as your work requires it + +--- + +# 3) Practices + +## 3.1) Naming +All modules are .NET projects based on .NET 10 (preview). Exclussion is the UI. It is based on Angular +All modules are contained by one or more projects. Each project goes in its dedicated folder. Each project starts with StellaOps.. In case it is common for for all StellaOps modules it is library or plugin and it is named StellaOps.. + +## 3.2) Key technologies & integrations + +- **Runtime**: .NET 10 (`net10.0`) preview SDK; C# latest preview features. +- **Data**: MongoDB (canonical store and job/export state). +- **Observability**: structured logs, counters, and (optional) OpenTelemetry traces. +- **Ops posture**: offline‑first, allowlist for remote hosts, strict schema validation, gated LLM fallback (only where explicitly configured). + +# 4) Modules +StellaOps is contained by different modules installable via docker containers +- Feedser. Responsible for aggregation and delivery of vulnerability database +- Cli. Command line tool to unlock full potential - request database operations, install scanner, request scan, configure backend +- Backend. Configures and Manages scans +- UI. UI to access the backend (and scanners) +- Agent. Installable daemon that does the scanning +- Zastava. Realtime monitor for allowed (verified) installations. + +## 4.1) Feedser +It is webservice based module that is responsible for aggregating vulnerabilities information from various sources, parsing and normalizing them into a canonical shape, merging and deduplicating the results in one place, with export capabilities to Json and TrivyDb. It supports init and resume for all of the sources, parse/normalize and merge/deduplication operations, plus export. Export supports delta exports—similarly to full and incremential database backups. + +### 4.1.1) Usage +It supports operations to be started by cmd line: +# stella db [fetch|merge|export] [init|resume ] +or +api available on https://db.stella-ops.org + +### 4.1.2) Data flow (end‑to‑end) + +1. **Fetch**: connectors request source windows with retries/backoff, persist raw documents with SHA256/ETag metadata. +2. **Parse & Normalize**: validate to DTOs (schema-checked), quarantine failures, normalize to canonical advisories (aliases, affected ranges with NEVRA/EVR/SemVer, references, provenance). +3. **Merge & Deduplicate**: enforce precedence, build/maintain alias graphs, compute deterministic hashes, and eliminate duplicates before persisting to MongoDB. +4. **Export**: JSON tree and/or Trivy DB; package and (optionally) push; write export state. + +### 4.1.3) Architecture +For more information of the architecture see `./docs/ARCHITECTURE_FEEDSER.md`. + +--- + +### 4.1.4) Glossary (quick) + +- **OVAL** — Vendor/distro security definition format; authoritative for OS packages. +- **NEVRA / EVR** — RPM and Debian version semantics for OS packages. +- **PURL / SemVer** — Coordinates and version semantics for OSS ecosystems. +- **KEV** — Known Exploited Vulnerabilities (flag only). + +--- +# 5) Your role as StellaOps contributor + +You acting as information technology engineer that will take different type of roles in goal achieving StellaOps production implementation +In order you to work - you have to be supplied with directory that contains `AGENTS.md`,`TASKS.md` files. There will you have more information about the role you have, the scope of your work and the tasks you will have. + +Boundaries: +- You operate only in the working directories I gave you, unless there is dependencies that makes you to work on dependency in shared directory. Then you ask for confirmation. + +You main characteristics: +- Keep endpoints small, deterministic, and cancellation-aware. +- Improve logs/metrics as per tasks. +- Update `TASKS.md` when moving tasks forward. +- When you are done with all task you state explicitly you are done. +- Impersonate the role described on working directory `AGENTS.md` you will read, if role is not available - take role of the CTO of the StellaOps in early stages. +- You always strive for best practices +- You always strive for re-usability +- When in doubt of design decision - you ask then act +- You are autonomus - meaning that you will work for long time alone and achieve maximum without stopping for stupid questions +- You operate on the same directory where other agents will work. In case you need to work on directory that is dependency on provided `AGENTS.md`,`TASKS.md` files you have to ask for confirmation first. + +## 5.1) Type of contributions + +- **BE‑Base (Platform & Pipeline)** + Owns DI, plugin host, job scheduler/coordinator, configuration binding, minimal API endpoints, and Mongo bootstrapping. +- **BE‑Conn‑X (Connectors)** + One agent per source family (NVD, Red Hat, Ubuntu, Debian, SUSE, GHSA, OSV, PSIRTs, CERTs, KEV, ICS). Implements fetch/parse/map with incremental watermarks. +- **BE‑Merge (Canonical Merge & Dedupe)** + Identity graph, precedence policies, canonical JSON serializer, and deterministic hashing (`merge_event`). +- **BE‑Export (JSON & Trivy DB)** + Deterministic export trees, Trivy DB packaging, optional ORAS push, and offline bundle. +- **QA (Validation & Observability)** + Schema tests, fixture goldens, determinism checks, metrics/logs/traces, e2e reproducibility runs. +- **DevEx/Docs** + Maintains this agent framework, templates, and per‑directory guides; assists parallelization and reviews. + + +## 5.2) Work-in-parallel rules (important) + +- **Directory ownership**: Each agent works **only inside its module directory**. Cross‑module edits require a brief handshake in issues/PR description. +- **Scoping**: Use each module’s `AGENTS.md` and `TASKS.md` to plan; autonomous agents must read `src/AGENTS.md` and the module docs before acting. +- **Determinism**: Sort keys, normalize timestamps to UTC ISO‑8601, avoid non‑deterministic data in exports and tests. +- **Status tracking**: Update your module’s `TASKS.md` as you progress (TODO → DOING → DONE/BLOCKED). +- **Tests**: Add/extend fixtures and unit tests per change; never regress determinism or precedence. +- **Test layout**: Use module-specific projects in `StellaOps.Feedser..Tests`; shared fixtures/harnesses live in `StellaOps.Feedser.Testing`. + +--- diff --git a/docs/07_HIGH_LEVEL_ARCHITECTURE.md b/docs/07_HIGH_LEVEL_ARCHITECTURE.md index 95d11737..fd8d5250 100755 --- a/docs/07_HIGH_LEVEL_ARCHITECTURE.md +++ b/docs/07_HIGH_LEVEL_ARCHITECTURE.md @@ -140,7 +140,7 @@ intra‑component reach‑ins. | `identity` | Embedded OAuth2/OIDC (OpenIddict 6) | MIT OpenIddict | `IIdentityProvider` for LDAP/SAML/JWT gateway | | `pluginloader` | Discover DLLs, SemVer gate, optional Cosign verify | Reflection + Cosign | `IPluginLifecycleHook` for telemetry | | `scanning` | SBOM‑ & image‑flow orchestration; runner pool | Trivy CLI (default) | `IScannerRunner` – e.g., Grype, Copacetic, Clair | -| `feedmerge` | Nightly NVD merge & feed enrichment | Hangfire job | drop‑in `*.Schedule.dll` for OSV, GHSA, NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU feeds | +| `feedser` (vulnerability ingest/merge/export service) | Nightly NVD merge & feed enrichment | Hangfire job | drop-in `*.Schedule.dll` for OSV, GHSA, NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU feeds | | `tls` | TLS provider abstraction | OpenSSL | `ITlsProvider` for custom suites (incl. **SM2**, where law or security requires it) | | `reporting` | Render HTML/PDF reports | RazorLight | `IReportRenderer` | | `ui` | Angular SPA & i18n | Angular {{ angular }} | new locales via `/locales/{lang}.json` | @@ -152,7 +152,7 @@ classDiagram class identity class pluginloader class scanning - class feedmerger + class feedser class tls class reporting class ui @@ -163,13 +163,13 @@ classDiagram configuration ..> identity : Uses identity ..> pluginloader : Authenticates Plugins pluginloader ..> scanning : Loads Scanner Runners - scanning ..> feedmerger : Triggers Feed Merges + scanning ..> feedser : Triggers Feed Merges tls ..> AllModules : Provides TLS Abstraction reporting ..> ui : Renders Reports for UI - scheduling ..> feedmerger : Schedules Nightly Jobs + scheduling ..> feedser : Schedules Nightly Jobs note for scanning "Pluggable: ISScannerRunner
e.g., Trivy, Grype" - note for feedmerger "Pluggable: *.Schedule.dll
e.g., OSV, GHSA Feeds" + note for feedser "Pluggable: *.Schedule.dll
e.g., OSV, GHSA Feeds" note for identity "Pluggable: IIdentityProvider
e.g., LDAP, SAML" note for reporting "Pluggable: IReportRenderer
e.g., Custom PDF" ``` @@ -220,30 +220,29 @@ Builder collects layer digests. `POST /layers/missing` → Redis SDIFF → missing layer list (< 20 ms). SBOM generated only for those layers and uploaded. -### 4.3 Feed Enrichment - -```mermaid -sequenceDiagram - participant CRON as Nightly Cron (Hangfire) - participant FM as Feed Merger - participant NVD as NVD Feed - participant OSV as OSV Plugin (Optional) - participant GHSA as GHSA Plugin (Optional) - participant REGC as Regional Catalogue Plugin (Optional) - participant REDIS as Redis (Merged Feed Storage) - participant UI as Web UI - - CRON->>FM: Trigger at 00:59 - FM->>NVD: Fetch & Merge NVD Data - alt Optional Plugins - FM->>OSV: Merge OSV Feed - FM->>GHSA: Merge GHSA Feed - FM->>REGC: Merge Regional Catalogue Feed - end - FM->>REDIS: Persist Merged Feed - REDIS-->>UI: Update Feed Freshness - UI->>UI: Display Green 'Feed Age' Tile -``` +### 4.3 Feedser Harvest & Export + +```mermaid +sequenceDiagram + participant SCHED as Feedser Scheduler + participant CONN as Source Connector Plug-in + participant FEEDSER as Feedser Core + participant MONGO as MongoDB (Canonical Advisories) + participant EXPORT as Exporter (JSON / Trivy DB) + participant ART as Artifact Store / Offline Kit + + SCHED->>CONN: Trigger window (init/resume) + CONN->>CONN: Fetch source documents + metadata + CONN->>FEEDSER: Submit raw document for parsing + FEEDSER->>FEEDSER: Parse & normalize to DTO + FEEDSER->>FEEDSER: Merge & deduplicate canonical advisory + FEEDSER->>MONGO: Write advisory, provenance, merge_event + FEEDSER->>EXPORT: Queue export delta request + EXPORT->>MONGO: Read canonical snapshot/deltas + EXPORT->>EXPORT: Build deterministic JSON & Trivy DB artifacts + EXPORT->>ART: Publish artifacts / Offline Kit bundle + ART-->>FEEDSER: Record export state + digests +``` ### 4.4 Identity & Auth Flow @@ -264,15 +263,15 @@ without Core changes. | Store | Primary Use | Why chosen | |----------------|-----------------------------------------------|--------------------------------| -| **Redis 7** | Queue, SBOM cache, Trivy DB mirror | Sub‑1 ms P99 latency | -| **MongoDB** | History > 180 d, audit logs, policy versions | Optional; document‑oriented | -| **Local tmpfs**| Trivy layer cache (`/var/cache/trivy`) | Keeps disk I/O off hot path | +| **MongoDB** | Feedser canonical advisories, merge events, export state | Deterministic canonical store with flexible schema | +| **Redis 7** | CLI quotas, short-lived job scheduling, layer diff cache | Sub-1 ms P99 latency for hot-path coordination | +| **Local tmpfs**| Trivy layer cache (`/var/cache/trivy`) | Keeps disk I/O off hot path | ```mermaid flowchart LR subgraph "Persistence Layers" - REDIS[(Redis: Fast Cache/Queues
Sub-1ms P99)] - MONGO[(MongoDB: Optional Audit/History
>180 Days)] + REDIS[(Redis: Quotas & Short-lived Queues
Sub-1ms P99)] + MONGO[(MongoDB: Canonical Advisories
Merge Events & Export State)] TMPFS[(Local tmpfs: Trivy Layer Cache
Low I/O Overhead)] end @@ -294,7 +293,7 @@ flowchart LR | **S‑1** | Pipeline Scan & Alert | Stella CLI → SBOM → `/scan` → policy verdict → CI exit code & link to *Scan Detail* | | **S‑2** | Mute Noisy CVE | Dev toggles **Mute** in UI → rule stored in Redis → next build passes | | **S‑3** | Nightly Re‑scan | `SbomNightly.Schedule` re‑queues SBOMs (mask‑filter) → dashboard highlights new Criticals | -| **S‑4** | Feed Update Cycle | `FeedMerge Service` merges feeds → UI *Feed Age* tile turns green | +| **S‑4** | Feed Update Cycle | `Feedser (vulnerability ingest/merge/export service)` refreshes feeds → UI *Feed Age* tile turns green | | **S‑5** | Custom Report Generation | Plug‑in registers `IReportRenderer` → `/report/custom/{digest}` → CI downloads artifact | ```mermaid diff --git a/docs/08_MODULE_SPECIFICATIONS.md b/docs/08_MODULE_SPECIFICATIONS.md index 27701fa1..53497ddd 100755 --- a/docs/08_MODULE_SPECIFICATIONS.md +++ b/docs/08_MODULE_SPECIFICATIONS.md @@ -1,371 +1,201 @@ -# 8 · Detailed Module Specifications — **Stella Ops** -_This document defines every backend/agent module that composes Stella Ops, -their public contracts, configuration keys and extension points._ - ---- - -## 0 Scope - -Describes **every .NET, and Angular project** that ships in the OSS Core, the plug‑in contracts they expose, and the runtime artefacts (Dockerfiles, Compose files) used to build and operate them. Commercial capabilities appear *only* as extension points. - ---- - -## 1 Repository Layout (flat) - -~~~text -src/ - │ docker-compose.yml - └─ docker-compose-library/ - │ ├─ docker-compose.no-deps.yml - │ ├─ docker-compose.dep.redis.yml - │ ├─ docker-compose.dep.mongo.yml - │ ├─ docker-compose.dep.proxy.yml - │ ├─ docker-compose.dep.repository.yml - │ └─ docker-compose.local.yml - └─ backend/ - │ ├─ Dockerfile - │ ├─ StellaOps.Web/ - │ ├─ StellaOps.Common/ - │ ├─ StellaOps.Plugins/ - │ ├─ StellaOps.Configuration/ - │ ├─ StellaOps.Localization/ - │ ├─ StellaOps.TlsProvider.OpenSSL/ - │ ├─ StellaOps.TlsProvider.OpenSSL.LegacyRegional/ - │ ├─ StellaOps.TlsProvider.Plugin.CustomTlsVendor/ - │ ├─ StellaOps.VulnerabilityDatabase/ - │ ├─ StellaOps.Scheduling/ - │ ├─ StellaOps.Scheduling.SbomsRescan/ - │ ├─ StellaOps.Scheduling.MutesExpire/ - │ ├─ StellaOps.Scheduling.Plugin.CommonCveFeed/ - │ ├─ StellaOps.Scheduling.Plugin.RegionalCatalogueFeed/ - │ ├─ StellaOps.Scanners.Trivy/ - │ ├─ StellaOps.Quota/ - │ ├─ StellaOps.Reporting/ - │ ├─ StellaOps.Notifications/ - │ ├─ StellaOps.Notifications.Email/ - │ ├─ StellaOps.Notifications.Plugin.MsTeams/ - │ ├─ StellaOps.Authority/ - │ ├─ StellaOps.Authority.AD/ - │ ├─ StellaOps.Cli/ - │ └─ StellaOps.Agent.Zastava/ - └─ frontend/ - ├─ Dockerfile - ├─ angular.json - ├─ stella-ops-ui/ - └─ libs/ - ├─ dashboard/ - ├─ scans/ - ├─ settings/ - ├─ core-ui/ - └─ i18n/ -~~~ - -All projects are referenced by **`StellaOps.sln`**; `dotnet publish -c Release -p:PublishSingleFile=true` builds a self‑contained **`StellaOps.Api`** binary (plug‑ins load at runtime). - ---- - -## 2 Shared Libraries - -| Project | Purpose | Key Interfaces | -|---------|---------|----------------| -| `StellaOps.Common` | Serilog sinks, Redis key helpers, DTO primitives. | `RedisKeys`, `Result` | -| `StellaOps.Plugins` | Plug‑in contracts + Cosign verification. | `IStellaPlugin`, `IScannerRunner`, `ITlsProvider`, `IScheduleJob` | -| `StellaOps.Localization` | Loads JSON locale bundles (backend & Angular). | `ILocaleProvider`, `CultureMiddleware` | - -Angular JSON‑bundle workflow matches the official i18n guide . - ---- - -## 3 Core Back‑end Projects - -| Project | Responsibility | Extensibility | -|---------|----------------|---------------| -| **`StellaOps.Api`** | ASP.NET host; source‑gen auto‑wires module endpoints. | Attributes `[MapRestController]`, `[MapHealth]`. | -| **`StellaOps.Configuration`** | Bind `appsettings.json` → typed options; `/health`. | `IConfigValidator`. | -| **`StellaOps.Quota`** | Enforces **Free‑tier quota** ({{ quota_token }}s scans/day) with early‑warning banner, 5 s soft back‑off, 60 s wait‑wall. | Swappable via `IQuotaStore` (e.g., Postgres). | -| **`StellaOps.JwtIssuer` *(new)* | Issues, refreshes and validates **Client‑JWTs**. For offline sites it produces a 30‑day token during OUK build and again on every OUK import. | `ITokenSigner` (e.g., HSM) | -| **`StellaOps.TlsProvider.OpenSSL`** | Default TLS suites. | New suites via `ITlsProvider` plug‑in. | -| **`StellaOps.TlsProvider.OpenSSL.LegacyRegional`** | . | — | -| **`StellaOps.VulnerabilityDatabase`** | Feed‑merge CLI writing Redis. | `IAdditionalFeedSource` (OSV, GHSA, regional catalogues). | -| **`StellaOps.Scheduling`** | Hangfire host inside API . | Jobs via `IScheduleJob`. | -| **`StellaOps.Scheduling.SbomsRescan`** | Nightly SBOM re‑scan (`0 2 * * *`). | — | -| **`StellaOps.Scheduling.MutesExpire`** | Daily mute expiry cleanup. | — | -| **`StellaOps.Scanners.Trivy`** | Trivy CLI for SBOM & image scans . | Other engines implement `IScannerRunner`. | -| **`StellaOps.Reporting`** | RazorLight HTML reports. | `IReportRenderer` for SARIF, CycloneDX. | -| **`StellaOps.Notifications`** | DI contracts for alerts. | `INotifier`. | -| **`StellaOps.Notifications.Email`** | SMTP channel. | — | -| **`StellaOps.Authority`** | OAuth2 / OIDC via OpenIddict 4 . | External IdPs via plug‑in. | -| **`StellaOps.Registry`** | read‑only Docker registry for agents + SBOM‑builder | Registry v2 (nginx‑hardened) | `IRegistryProvider` | -| **`StellaOps.MutePolicies`** | store YAML / Rego policies, validate & version | MongoDB + Redis | `IPolicyStore` | -| **`StellaOps.Attestor`** *(TODO)*| SLSA provenance + Rekor verification | Sigstore Rekor | `IAttestor` | - -## 3 · Module Details - -> _Only contracts and configuration that may change in the next two quarters are shown; for stable, unchanging keys see the inline XML‑doc in the codebase._ - -### 3.1. StellaOps.Configuration - -* **Responsibility** – parse environment variables or `appsettings.json`; expose `/health`, `/metrics`. -* **Key extension point** – `IConfigValidator` → validate & normalise custom settings before DI builds. - -### 3.2. StellaOps.Authority - -* **Responsibility** – ship with OpenIddict 6, supporting *client‑credentials* and *password* grants. -* `IIdentityProvider` plug‑in can delegate token issuance to LDAP, SAML, Keycloak … - - -### 3.3. StellaOps.Scanners - -* **Primary flow** – SBOM‑first; falls back to image‑unpack if SBOM absent. -* **Multi‑Format Support** – side‑car `.sbom.type` file; auto‑detects (`SPDXID:` or `bomFormat` heuristics). -* **Delta Layer Workflow** – `POST /layers/missing` (`SET DIFF` on Redis) responds < 20 ms; Stella CLI passes only new layers. -* **Plug‑in contract evolution** - -```csharp -// current -Task RunAsync(Stream sbomJson, CancellationToken ct); - -// v2 (preferred) -Task RunAsync(Stream sbom, SbomFormat fmt, CancellationToken ct); -``` - -### 3.5 StellOps.Registry - -* **Purpose** – internal, anonymous **read‑only** Docker registry to avoid GHCR / Docker Hub pulls. -* **Deployment** – container `stellops.registry:2`; mounted volume `/var/lib/registry`; optional TLS via env vars. - -| Key | Default | Notes | -|----------------------------------|---------|---------------------------------| -| `REGISTRY_READONLY` | `true` | Forces 403 on PUT, 405 on DELETE | -| `REGISTRY_STORAGE_DELETE_ENABLED`| `false` | Immutable tags | - -**Plug‑in contract** — `IRegistryProvider.PullAsync(string imageRef)` for mapping to Artifactory, Harbor, etc. - ---- - -### 3.6 StellaOps.MutePolicies - -* **Purpose** – central Policy‑as‑Code store (YAML v1 now, Rego soon). -* **Persistence** – current live rules in Redis (`policies:active`); immutable commits in Mongo `policies_history`. - -| REST verb | Path | Description | -|-----------|---------------------|---------------------------| -| `GET` | `/policy/export` | download active YAML | -| `POST` | `/policy/import` | upload YAML / Rego file | -| `POST` | `/policy/validate` | lint without persisting | - -**CLI** – Stella CLI gains `--policy-file scan-policy.yaml`. - -**Plug‑in contract** — `IPolicyStore` for GitOps back‑ends, Vault, etc. - ---- - -### 3.7. StellaOps.Attestor *(Planned – Q1‑2026)* - -Handles SLSA provenance docs and Rekor log verification. - -```csharp -public interface IAttestor { - Task CreateAsync(ImageRef img, Sbom sbom); - Task VerifyAsync(ProvenanceDoc doc); -} -``` - -### 3.7. StellaOps.FeedMerge.Service - -Nightly Hangfire job (01:00) merges NVD JSON; plug‑ins can provide ISourceFeed for OSV, GHSA, NVD, CNNVD, CNVD, ENISA and BDU feeds. - -### 3.8. StellOps.Tls - -Abstracts TLS stack; default OpenSSL; `ITlsProvider` lets enterprises swap in custom suites—**including SM2, where law or security requires it**. - -### 3.9. StellaOps.Reporting - -HTML / PDF generation via RazorLight; custom renderers via IReportRenderer. - -### 3.10 UI - -Angular 17 SPA; lazy‑loaded feature modules, standalone component routes for UI plug‑ins. - -Static Go daemon / k8s DaemonSet; watches Docker/CRI‑O events; uploads SBOMs; optional enforce mode via policy plug‑in. - -### 3.11 StellaOps.Quota — **Free‑Tier Daily Quota Service** - -**Responsibility** - -* Track per‑token scan count (`quota:` key in Redis). -* Reset counters at **00:00 UTC** with key TTL. -* Inject HTTP headers - * `X‑Stella‑Quota‑Remaining` - * `X‑Stella‑Reset` -* Apply adaptive throttling: - * scans 90% of {{ quota_token }}; - * scans 10% of the max daily → UI banner flag `X‑Stella‑Quota‑Warn:true`; - * scans ≥ {{ quota_token }}  → reply is slower. -* **Offline token awareness** — if `token.valid == false` and - `OfflineMode == true`, return HTTP *451 ComplianceBlock* so that CLI gives a - clear actionable error. -* New config: - -```json -"Quota": { - "OfflineGraceDays": 7 // show banner this many days before token expiry -} -``` - -**Interface** - -```csharp -public interface IQuotaService -{ - /// Returns true when the call is allowed. - Task CheckAsync(string token, CancellationToken ct); -} - -public readonly record struct QuotaVerdict( - bool IsAllowed, - int Remaining, - DateTimeOffset ResetUtc, - TimeSpan RetryAfter); -``` - -**Configuration** (`appsettings.json` keys) - -```json -"Quota": { - "FreeTierDailyLimit": {{ quota_token }} , - "WarnThreshold": 200, - "SoftRetrySeconds": 5, - "HardRetrySeconds": 60 -} -``` - -**Extensibility** - -* Override storage by providing an `IQuotaStore` plug‑in for Postgres or Mongo. -* UI plug‑ins can subscribe to SSE `/quota/events` for custom dashboards. - -### 3.12 StellaOps.JwtIssuer — new section - -|API |Path| Notes| -|-----|----|-------| -|`POST /token/offline` | Admin‑only. | Generates a 30 d Client‑JWT for air‑gapped clusters; returns ZIP that the admin can copy to the target host. - -*OUK hook* - -* OUK builder calls JwtIssuer.SignOfflineToken(exp=+30d). -* Drops client.jwt into ouk/root/. -* Backend OUK importer places file under /var/lib/stella/tokens/. - ---- - -## 4 · Compose / Helm Snippet (reference) - -```yaml -services: - registry: - image: stellops.registry:2 - restart: unless-stopped - environment: - REGISTRY_READONLY: "true" - volumes: - - ./_registry:/var/lib/registry - ports: - - "5000:5000" - - backend: - image: registry.local/stellops/backend:${TAG} - depends_on: [registry, redis] -``` - ---- - -## 4 Plug‑ins (sign‑required) - -| Plug‑in | Contract | Notes | -|---------|----------|-------| -| `StellaOps.Notifications.Plugin.MsTeams` | `INotifier` | Sends cards to Teams webhooks. | -| `StellaOps.Authority.AD` | `IIdentityProvider` | LDAP/Active‑Directory token issue. | -| `StellaOps.Scheduling.Plugin.CommonCveFeed` | `IScheduleJob` | Merges OSV & NVD JSON hourly . | -| `StellaOps.Scheduling.Plugin.RegionalCatalogueFeed` | `IScheduleJob` | Imports NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU XML daily. | -| `StellaOps.TlsProvider.Plugin.CustomTlsVendor` | `ITlsProvider` | Binds regional specific shared libs. | - -Cosign signatures are mandatory; loader rejects unsigned DLLs when `DisableUnsigned=false`. - ---- - -## 5 Agents - -### 5.1 `StellaOps.Cli` - -Distroless CLI; -Returns exit‑code 1 on policy violation, enabling CI blocking. -* **Role** – CI helper: Build SBOM, call `/scan`, exit non‑zero on high severity. -* **Flags** – `--engine`, `--threshold`, `--registry-pull-token`, `--pdf-out`, `--delta`, `--sbom-type`, `--policy-file.`. -* **Auth** – OAuth2 *scanner* scope. - -### 5.2 `StellaOps.Agent.Zastava` - -* **Role** – Passive container inventory → uploads SBOMs via `/agent/sbom`. -* **Modes** – `off`, `inventory` (Core default). -* No kernel driver (unlike Falco) . - ---- - -## 6 Angular Front‑end - -| Package | Path | Feature | Lazy | -|---------|------|---------|------| -| **App** | `frontend/stella-ops-ui/` | Shell, auth guards. | — | -| `dashboard` | `libs/dashboard/` | Live metrics tiles. | ✔ | -| `scans` | `libs/scans/` | List, detail, mute, diff. | ✔ | -| `settings` | `libs/settings/` | Feed cron, workers, TLS switch. | ✔ | -| `core-ui` | `libs/core-ui/` | Tailwind components. | — | -| `i18n` | `libs/i18n/` | Runtime locale switch, pipe. | — | - -Lazy loading of workspace libs follows Nx/Angular guidance . - ---- - -## 7 Docker Artefacts - -### 7.1 Dockerfiles - -* **`backend/Dockerfile`** – multi‑stage .NET {{ dotnet }}; single‑file publish; distroless runtime . -* **`frontend/Dockerfile`** – Node 20 build → Nginx static serve. -* Every plug‑in repo may include its own Dockerfile when shipping side‑cars (e.g., custom scanner). - -### 7.2 Compose Stacks - -* **`docker-compose.yml`** - * Extends above with Redis 7 and Mongo 7 for small on‑prem installs. - -* **`docker-compose.no-deps.yml`** - * backend, frontend, Trivy, Maven proxy. - * Assumes external Redis & Mongo. - -* **`docker-compose.local.yml`** - * Build images from local source and bring up backend, frontend, Redis, Mongo, Trivy, Maven proxy for dev‑loop. - -Docker Compose override precedence matches official docs . - ---- - -## 8 Performance Budget - -| Flow | P95 target | Bottleneck | -|------|-----------:|-----------| -| SBOM fast‑path | ≤ 5 s | Redis queue depth (keep P99 < 1 ms)  | -| Image‑unpack | ≤ 10 s | Trivy layer unpack. | -| Nightly re‑scan | 80 SBOM/s | Runner CPU. | - ---- - -## Change Log - -| Version | Date | Notes | -|---------|------|-------| -| **v2.2** | 2025‑07‑11 | Flat layout; stella‑ops‑ui naming; Dockerfiles & 3 Compose stacks; agents and localisation library. | -| v2.1 | 2025‑07‑11 | First flat‑structure draft. | - -*(End of Module Specifications v2.2‑core)* +# 8 · Detailed Module Specifications — **Stella Ops Feedser** +_This document describes the Feedser service, its supporting libraries, connectors, exporters, and test assets that live in the OSS repository._ + +--- + +## 0 Scope + +Feedser is the vulnerability ingest/merge/export subsystem of Stella Ops. It +fetches primary advisories, normalizes and deduplicates them into MongoDB, and +produces deterministic JSON and Trivy DB exports. This document lists the +projects that make up that workflow, the extension points they expose, and the +artefacts they ship. + +--- + +## 1 Repository layout (current) + +```text +src/ + ├─ Directory.Build.props / Directory.Build.targets + ├─ StellaOps.Plugin/ + ├─ StellaOps.Feedser.Core/ + ├─ StellaOps.Feedser.Core.Tests/ + ├─ StellaOps.Feedser.Models/ (+ .Tests/) + ├─ StellaOps.Feedser.Normalization/ (+ .Tests/) + ├─ StellaOps.Feedser.Merge/ (+ .Tests/) + ├─ StellaOps.Feedser.Storage.Mongo/ (+ .Tests/) + ├─ StellaOps.Feedser.Exporter.Json/ (+ .Tests/) + ├─ StellaOps.Feedser.Exporter.TrivyDb/ (+ .Tests/) + ├─ StellaOps.Feedser.Source.* / StellaOps.Feedser.Source.*.Tests/ + ├─ StellaOps.Feedser.Testing/ + ├─ StellaOps.Feedser.Tests.Shared/ + ├─ StellaOps.Feedser.WebService/ (+ .Tests/) + ├─ PluginBinaries/ + └─ StellaOps.Feedser.sln +``` + +Each folder is a .NET project (or set of projects) referenced by +`StellaOps.Feedser.sln`. Build assets are shared through the root +`Directory.Build.props/targets` so conventions stay consistent. + +--- + +## 2 Shared libraries + +| Project | Purpose | Key extension points | +|---------|---------|----------------------| +| `StellaOps.Plugin` | Base contracts for connectors, exporters, and DI routines plus Cosign validation helpers. | `IFeedConnector`, `IExporterPlugin`, `IDependencyInjectionRoutine` | +| `StellaOps.DependencyInjection` | Composable service registrations for Feedser and plug-ins. | `IDependencyInjectionRoutine` discovery | +| `StellaOps.Feedser.Testing` | Common fixtures, builders, and harnesses for integration/unit tests. | `FeedserMongoFixture`, test builders | +| `StellaOps.Feedser.Tests.Shared` | Shared assembly metadata and fixtures wired in via `Directory.Build.props`. | Test assembly references | + +--- + +## 3 Core projects + +| Project | Responsibility | Extensibility | +|---------|----------------|---------------| +| `StellaOps.Feedser.WebService` | ASP.NET Core minimal API hosting Feedser jobs, status endpoints, and scheduler. | DI-based plug-in discovery; configuration binding | +| `StellaOps.Feedser.Core` | Job orchestration, connector pipelines, merge workflows, export coordination. | `IFeedConnector`, `IExportJob`, deterministic merge policies | +| `StellaOps.Feedser.Models` | Canonical advisory DTOs and enums persisted in MongoDB and exported artefacts. | Partial classes for source-specific metadata | +| `StellaOps.Feedser.Normalization` | Version comparison, CVSS normalization, text utilities for canonicalization. | Helpers consumed by connectors/merge | +| `StellaOps.Feedser.Merge` | Precedence evaluation, alias graph maintenance, merge-event hashing. | Policy extensions via DI | +| `StellaOps.Feedser.Storage.Mongo` | Repository layer for documents, DTOs, advisories, merge events, export state. | Connection string/config via options | +| `StellaOps.Feedser.Exporter.Json` | Deterministic vuln-list JSON export pipeline. | Dependency injection for storage + plugin to host | +| `StellaOps.Feedser.Exporter.TrivyDb` | Builds Trivy DB artefacts from canonical advisories. | Optional ORAS push routines | + +### 3.1 StellaOps.Feedser.WebService + +* Hosts minimal API endpoints (`/health`, `/status`, `/jobs`). +* Runs the scheduler that triggers connectors and exporters according to + configured windows. +* Applies dependency-injection routines from `PluginBinaries/` at startup only + (restart-time plug-ins). + +### 3.2 StellaOps.Feedser.Core + +* Defines job primitives (fetch, parse, map, merge, export) used by connectors. +* Coordinates deterministic merge flows and writes `merge_event` documents. +* Provides telemetry/log scopes consumed by WebService and exporters. + +### 3.3 StellaOps.Feedser.Storage.Mongo + +* Persists raw documents, DTO records, canonical advisories, aliases, affected + packages, references, merge events, export state, and job leases. +* Exposes repository helpers for exporters to stream full/delta snapshots. + +### 3.4 StellaOps.Feedser.Exporter.* + +* `Exporter.Json` mirrors the Aqua vuln-list tree with canonical ordering. +* `Exporter.TrivyDb` builds Trivy DB Bolt archives and optional OCI bundles. +* Both exporters honour deterministic hashing and respect export cursors. + +--- + +## 4 Source connectors + +Connectors live under `StellaOps.Feedser.Source.*` and conform to the interfaces +in `StellaOps.Plugin`. + +| Family | Project(s) | Notes | +|--------|------------|-------| +| Distro PSIRTs | `StellaOps.Feedser.Source.Distro.*` | Debian, Red Hat, SUSE, Ubuntu connectors with NEVRA/EVR helpers. | +| Vendor PSIRTs | `StellaOps.Feedser.Source.Vndr.*` | Adobe, Apple, Cisco, Chromium, Microsoft, Oracle, VMware. | +| Regional CERTs | `StellaOps.Feedser.Source.Cert*`, `Source.Ru.*`, `Source.Ics.*`, `Source.Kisa` | Provide enrichment metadata while preserving vendor precedence. | +| OSS ecosystems | `StellaOps.Feedser.Source.Ghsa`, `Source.Osv`, `Source.Cve`, `Source.Kev`, `Source.Acsc`, `Source.Cccs`, `Source.Jvn` | Emit SemVer/alias-rich advisories. | + +Each connector ships fixtures/tests under the matching `*.Tests` project. + +--- + +## 5 · Module Details + +> _Focus on the Feedser-specific services that replace the legacy FeedMerge cron._ + +### 5.1 Feedser.Core + +* Owns the fetch → parse → merge → export job pipeline and enforces deterministic + merge hashes (`merge_event`). +* Provides `JobSchedulerBuilder`, job coordinator, and telemetry scopes consumed + by the WebService and exporters. + +### 5.2 Feedser.Storage.Mongo + +* Bootstrapper creates collections/indexes (documents, dto, advisory, alias, + affected, merge_event, export_state, jobs, locks). +* Repository APIs surface full/delta advisory reads for exporters, plus + SourceState and job lease persistence. + +### 5.3 Feedser.Exporter.Json / Feedser.Exporter.TrivyDb + +* JSON exporter mirrors vuln-list layout with per-file digests and manifest. +* Trivy DB exporter shells or native-builds Bolt archives, optionally pushes OCI + layers, and records export cursors. + +### 5.4 Feedser.WebService + +* Minimal API host exposing `/health`, `/ready`, `/jobs` and wiring telemetry. +* Loads restart-time plug-ins from `PluginBinaries/`, executes Mongo bootstrap, + and registers built-in connectors/exporters with the scheduler. + +### 5.5 Plugin host & DI bridge + +* `StellaOps.Plugin` + `StellaOps.DependencyInjection` provide the contracts and + helper routines for connectors/exporters to integrate with the WebService. + +--- + +## 6 · Plug-ins & Agents + +* **Plug-in discovery** – restart-only; the WebService enumerates + `PluginBinaries/` (or configured directories) and executes the contained + `IDependencyInjectionRoutine` implementations. +* **Connector/exporter packages** – each source/exporter can ship as a plug-in + assembly with its own options and HttpClient configuration, keeping the core + image minimal. +* **Stella CLI (agent)** – triggers feed-related jobs (`stella db fetch/merge/export`) + and consumes the exported JSON/Trivy DB artefacts, aligning with the SBOM-first + workflow described in `AGENTS.md`. +* **Offline Kit** – bundles Feedser plug-ins, JSON tree, Trivy DB, and export + manifests so air-gapped sites can load the latest vulnerability data without + outbound connectivity. + +--- + +## 7 · Docker & Distribution Artefacts + +| Artefact | Path / Identifier | Notes | +|----------|-------------------|-------| +| Feedser WebService image | `containers/feedser/Dockerfile` (built via CI) | Self-contained ASP.NET runtime hosting scheduler/endpoints. | +| Plugin bundle | `PluginBinaries/` | Mounted or baked-in assemblies for connectors/exporters. | +| Offline Kit tarball | Produced by CI release pipeline | Contains JSON tree, Trivy DB OCI layout, export manifest, and plug-ins. | +| Local dev compose | `scripts/` + future compose overlays | Developers can run MongoDB, Redis (optional), and WebService locally. | + +--- + +## 8 · Performance Budget + +| Scenario | Budget | Source | +|----------|--------|--------| +| Advisory upsert (large advisory) | ≤ 500 ms/advisory | `AdvisoryStorePerformanceTests` (Mongo) | +| Advisory fetch (`GetRecent`) | ≤ 200 ms/advisory | Same performance test harness | +| Advisory point lookup (`Find`) | ≤ 200 ms/advisory | Same performance test harness | +| Bulk upsert/fetch cycle | ≤ 28 s total for 30 large advisories | Same performance test harness | +| Feedser job scheduling | Deterministic cron execution via `JobSchedulerHostedService` | `StellaOps.Feedser.Core` tests | +| Trivy DB export | Deterministic digests across runs (ongoing TODO for end-to-end test) | `Exporter.TrivyDb` backlog | + +Budgets are enforced in automated tests where available; outstanding TODO/DOING +items (see task boards) continue tracking gaps such as exporter determinism. + +--- + +## 9 Testing + +* Unit and integration tests live alongside each component (`*.Tests`). +* Shared fixtures come from `StellaOps.Feedser.Testing` and + `StellaOps.Feedser.Tests.Shared` (linked via `Directory.Build.props`). +* Integration suites use ephemeral MongoDB and Redis via Testcontainers to + validate end-to-end flow without external dependencies. + +--- diff --git a/docs/13_SECURITY_POLICY.md b/docs/13_SECURITY_POLICY.md index fa8b96d4..1956a68d 100755 --- a/docs/13_SECURITY_POLICY.md +++ b/docs/13_SECURITY_POLICY.md @@ -81,7 +81,7 @@ cosign verify \ ## 5 · Private‑feed mirrors 🌐 -The **FeedMerge** service provides a signed SQLite snapshot merging: +The **Feedser (vulnerability ingest/merge/export service)** provides signed JSON and Trivy DB snapshots that merge: * OSV + GHSA * (optional) NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU regionals @@ -98,4 +98,4 @@ We are grateful to the researchers who help keep Stella Ops safe: | ------- | ------------------ | ------------ | | *empty* | *(your name here)* | | ---- \ No newline at end of file +--- diff --git a/docs/14_GLOSSARY_OF_TERMS.md b/docs/14_GLOSSARY_OF_TERMS.md index cd57f492..300c0ade 100755 --- a/docs/14_GLOSSARY_OF_TERMS.md +++ b/docs/14_GLOSSARY_OF_TERMS.md @@ -20,7 +20,7 @@ open a PR and append it alphabetically.* | **ADR** | *Architecture Decision Record* – lightweight Markdown file that captures one irreversible design decision. | ADR template lives at `/docs/adr/` | | **AIRE** | *AI Risk Evaluator* – optional Plus/Pro plug‑in that suggests mute rules using an ONNX model. | Commercial feature | | **Azure‑Pipelines** | CI/CD service in Microsoft Azure DevOps. | Recipe in Pipeline Library | -| **BDU** | Russian (FSTEC) national vulnerability database: *База данных уязвимостей*. | Merged with NVD by FeedMerge Service | +| **BDU** | Russian (FSTEC) national vulnerability database: *База данных уязвимостей*. | Merged with NVD by Feedser (vulnerability ingest/merge/export service) | | **BuildKit** | Modern Docker build engine with caching and concurrency. | Needed for layer cache patterns | | **CI** | *Continuous Integration* – automated build/test pipeline. | Stella integrates via CLI | | **Cosign** | Open‑source Sigstore tool that signs & verifies container images **and files**. | Images & OUK tarballs | @@ -36,7 +36,7 @@ open a PR and append it alphabetically.* | **Digest (image)** | SHA‑256 hash uniquely identifying a container image or layer. | Pin digests for reproducible builds | | **Docker‑in‑Docker (DinD)** | Running Docker daemon inside a CI container. | Used in GitHub / GitLab recipes | | **DTO** | *Data Transfer Object* – C# record serialised to JSON. | Schemas in doc 11 | -| **FeedMerge service** | Background job that merges OVN, GHSA and NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU XML into Redis. | Cron default `0 1 * * *` | +| **Feedser** | Vulnerability ingest/merge/export service consolidating OVN, GHSA, NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU feeds into the canonical MongoDB store and export artifacts. | Cron default `0 1 * * *` | | **FSTEC** | Russian regulator issuing SOBIT certificates. | Pro GA target | | **Gitea** | Self‑hosted Git service – mirrors GitHub repo. | OSS hosting | | **GOST TLS** | TLS cipher‑suites defined by Russian GOST R 34.10‑2012 / 34.11‑2012. | Provided by `OpenSslGost` or CryptoPro | diff --git a/docs/17_SECURITY_HARDENING_GUIDE.md b/docs/17_SECURITY_HARDENING_GUIDE.md index 49a774c0..068c01bb 100755 --- a/docs/17_SECURITY_HARDENING_GUIDE.md +++ b/docs/17_SECURITY_HARDENING_GUIDE.md @@ -150,7 +150,7 @@ cosign verify ghcr.io/stellaops/backend@sha256: \ | Layer | Cadence | Method | | -------------------- | -------------------------------------------------------- | ------------------------------ | | Backend & CLI images | Monthly or CVE‑driven docker pull + docker compose up -d | -| Trivy DB | 24 h cron via FeedMerge Service | configurable (FeedMerge.Cron) | +| Trivy DB | 24 h scheduler via Feedser (vulnerability ingest/merge/export service) | configurable via Feedser scheduler options | | Docker Engine | vendor LTS | distro package manager | | Host OS | security repos enabled | unattended‑upgrades | diff --git a/docs/19_TEST_SUITE_OVERVIEW.md b/docs/19_TEST_SUITE_OVERVIEW.md index 60982513..b4208672 100755 --- a/docs/19_TEST_SUITE_OVERVIEW.md +++ b/docs/19_TEST_SUITE_OVERVIEW.md @@ -16,7 +16,7 @@ contributors who need to extend coverage or diagnose failures. | **1. Unit** | `xUnit` (dotnet test) | `*.Tests.csproj` | per PR / push | | **2. Property‑based** | `FsCheck` | `SbomPropertyTests` | per PR | | **3. Integration (API)** | `Testcontainers` suite | `test/Api.Integration` | per PR + nightly | -| **4. Integration (DB‑merge)** | in‑memory Mongo + Redis | `FeedMerge.Integration` | per PR | +| **4. Integration (DB-merge)** | in-memory Mongo + Redis | `Feedser.Integration` (vulnerability ingest/merge/export service) | per PR | | **5. Contract (gRPC)** | `Buf breaking` | `buf.yaml` files | per PR | | **6. Front‑end unit** | `Jest` | `ui/src/**/*.spec.ts` | per PR | | **7. Front‑end E2E** | `Playwright` | `ui/e2e/**` | nightly | @@ -70,7 +70,7 @@ flowchart LR I1 --> FE[Jest] FE --> E2E[Playwright] E2E --> Lighthouse - Lighthouse --> INTEG2[FeedMerge] + Lighthouse --> INTEG2[Feedser] INTEG2 --> LOAD[k6] LOAD --> CHAOS[pumba] CHAOS --> RELEASE[Attestation diff] diff --git a/docs/40_ARCHITECTURE_OVERVIEW.md b/docs/40_ARCHITECTURE_OVERVIEW.md index 2dbb18b2..15b2c05c 100755 --- a/docs/40_ARCHITECTURE_OVERVIEW.md +++ b/docs/40_ARCHITECTURE_OVERVIEW.md @@ -32,7 +32,7 @@ why the system leans *monolith‑plus‑plug‑ins*, and where extension points graph TD A(API Gateway) B1(Scanner Core
.NET latest LTS) - B2(FeedMerge service) + B2(Feedser service\n(vuln ingest/merge/export)) B3(Policy Engine OPA) C1(Redis 7) C2(MongoDB 7) @@ -53,7 +53,7 @@ graph TD | ---------------------------- | --------------------- | ---------------------------------------------------- | | **API Gateway** | ASP.NET Minimal API | Auth (JWT), quotas, request routing | | **Scanner Core** | C# 12, Polly | Layer diffing, SBOM generation, vuln correlation | -| **FeedMerge** | C# source‑gen workers | Consolidate NVD + regional CVE feeds into one SQLite | +| **Feedser (vulnerability ingest/merge/export service)** | C# source-gen workers | Consolidate NVD + regional CVE feeds into the canonical MongoDB store and drive JSON / Trivy DB exports | | **Policy Engine** | OPA (Rego) | admission decisions, custom org rules | | **Redis 7** | Key‑DB compatible | LRU cache, quota counters | | **MongoDB 7** | WiredTiger | SBOM & findings storage | @@ -121,7 +121,7 @@ Hot‑plugging is deferred until after v 1.0 for security review. Although the default deployment is a single container, each sub‑service can be extracted: -* FeedMerge → standalone cron pod. +* Feedser → standalone cron pod. * Policy Engine → side‑car (OPA) with gRPC contract. * ResultSink → queue worker (RabbitMQ or Azure Service Bus). diff --git a/src/StellaOps.Feedser/ARCHITECTURE.md b/docs/ARCHITECTURE_FEEDSER.md similarity index 96% rename from src/StellaOps.Feedser/ARCHITECTURE.md rename to docs/ARCHITECTURE_FEEDSER.md index 19d25d13..f1df1515 100644 --- a/src/StellaOps.Feedser/ARCHITECTURE.md +++ b/docs/ARCHITECTURE_FEEDSER.md @@ -1,21 +1,20 @@ -@ -1,191 +0,0 @@ # ARCHITECTURE.md — **StellaOps.Feedser** > **Goal**: Build a sovereign-ready, self-hostable **feed-merge service** that ingests authoritative vulnerability sources, normalizes and de-duplicates them into **MongoDB**, and exports **JSON** and **Trivy-compatible DB** artifacts. -> **Form factor**: Long-running **Web Service** with **REST APIs** (health, status, control) and an embedded **internal cron scheduler**. +> **Form factor**: Long-running **Web Service** with **REST APIs** (health, status, control) and an embedded **internal cron scheduler**. Controllable by StellaOps.Cli (# stella db ...) > **No signing inside Feedser** (signing is a separate pipeline step). > **Runtime SDK baseline**: .NET 10 Preview 7 (SDK 10.0.100-preview.7.25380.108) targeting `net10.0`, aligned with the deployed api.stella-ops.org service. -> **Three explicit stages**: +> **Four explicit stages**: > > 1. **Source Download** → raw documents. -> 2. **Merge + Dedupe + Normalization** → MongoDB canonical. -> 3. **Export** → JSON or TrivyDB (full or delta), then (externally) sign/publish. +> 2. **Parse & Normalize** → schema-validated DTOs enriched with canonical identifiers. +> 3. **Merge & Deduplicate** → precedence-aware canonical records persisted to MongoDB. +> 4. **Export** → JSON or TrivyDB (full or delta), then (externally) sign/publish. --- ## 1) Naming & Solution Layout -**Solution root**: `StellaOps.Feedser` **Source connectors** namespace prefix: `StellaOps.Feedser.Source.*` **Exporters**: diff --git a/scripts/render_docs.py b/scripts/render_docs.py new file mode 100644 index 00000000..efefbb03 --- /dev/null +++ b/scripts/render_docs.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +"""Render Markdown documentation under docs/ into a static HTML bundle. + +The script converts every Markdown file into a standalone HTML document, +mirroring the original folder structure under the output directory. A +`manifest.json` file is also produced to list the generated documents and +surface basic metadata (title, source path, output path). + +Usage: + python scripts/render_docs.py --source docs --output build/docs-site + +Dependencies: + pip install markdown pygments +""" + +from __future__ import annotations + +import argparse +import json +import logging +import os +import shutil +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Iterable, List + +import markdown + +# Enable fenced code blocks, tables, and definition lists. These cover the +# Markdown constructs heavily used across the documentation set. +MD_EXTENSIONS = [ + "fenced_code", + "codehilite", + "tables", + "toc", + "def_list", + "admonition", +] + +HTML_TEMPLATE = """ + + + + + {title} + + + +
+{body} +
+
+

Generated on {generated_at} UTC · Source: {source}

+
+ + +""" + + +@dataclass +class DocEntry: + source: Path + output: Path + title: str + + def to_manifest(self) -> dict[str, str]: + return { + "source": self.source.as_posix(), + "output": self.output.as_posix(), + "title": self.title, + } + + +def discover_markdown_files(source_root: Path) -> Iterable[Path]: + for path in source_root.rglob("*.md"): + if path.is_file(): + yield path + + +def read_title(markdown_text: str, fallback: str) -> str: + for raw_line in markdown_text.splitlines(): + line = raw_line.strip() + if line.startswith("#"): + return line.lstrip("#").strip() or fallback + return fallback + + +def convert_markdown(path: Path, source_root: Path, output_root: Path) -> DocEntry: + relative = path.relative_to(source_root) + output_path = output_root / relative.with_suffix(".html") + output_path.parent.mkdir(parents=True, exist_ok=True) + + text = path.read_text(encoding="utf-8") + html_body = markdown.markdown(text, extensions=MD_EXTENSIONS) + + title = read_title(text, fallback=relative.stem.replace("_", " ")) + generated_at = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + + output_path.write_text( + HTML_TEMPLATE.format( + title=title, + body=html_body, + generated_at=generated_at, + source=relative.as_posix(), + ), + encoding="utf-8", + ) + + return DocEntry(source=relative, output=output_path.relative_to(output_root), title=title) + + +def copy_static_assets(source_root: Path, output_root: Path) -> None: + for path in source_root.rglob("*"): + if path.is_dir() or path.suffix.lower() == ".md": + # Skip Markdown (already rendered separately). + continue + relative = path.relative_to(source_root) + destination = output_root / relative + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(path.read_bytes()) + logging.info("Copied asset %s", relative) + + +def write_manifest(entries: Iterable[DocEntry], output_root: Path) -> None: + manifest_path = output_root / "manifest.json" + manifest = [entry.to_manifest() for entry in entries] + manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + logging.info("Wrote manifest with %d entries", len(manifest)) + + +def write_index(entries: List[DocEntry], output_root: Path) -> None: + index_path = output_root / "index.html" + generated_at = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + + items = "\n".join( + f"
  • {entry.title}" f" · {entry.source.as_posix()}
  • " + for entry in sorted(entries, key=lambda e: e.title.lower()) + ) + + html = f""" + + + + + Stella Ops Documentation Index + + + +

    Stella Ops Documentation

    +

    Generated on {generated_at} UTC

    +
      +{items} +
    + + +""" + index_path.write_text(html, encoding="utf-8") + logging.info("Wrote HTML index with %d entries", len(entries)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Render documentation bundle") + parser.add_argument("--source", default="docs", type=Path, help="Directory containing Markdown sources") + parser.add_argument("--output", default=Path("build/docs-site"), type=Path, help="Directory for rendered output") + parser.add_argument("--clean", action="store_true", help="Remove the output directory before rendering") + return parser.parse_args() + + +def main() -> int: + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") + args = parse_args() + + source_root: Path = args.source.resolve() + output_root: Path = args.output.resolve() + + if not source_root.exists(): + logging.error("Source directory %s does not exist", source_root) + return os.EX_NOINPUT + + if args.clean and output_root.exists(): + logging.info("Cleaning existing output directory %s", output_root) + shutil.rmtree(output_root) + + output_root.mkdir(parents=True, exist_ok=True) + + entries: List[DocEntry] = [] + for md_file in discover_markdown_files(source_root): + entry = convert_markdown(md_file, source_root, output_root) + entries.append(entry) + logging.info("Rendered %s -> %s", entry.source, entry.output) + + write_manifest(entries, output_root) + write_index(entries, output_root) + copy_static_assets(source_root, output_root) + + logging.info("Documentation bundle available at %s", output_root) + return os.EX_OK + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/StellaOps.Feedser/Directory.Build.props b/src/Directory.Build.props similarity index 95% rename from src/StellaOps.Feedser/Directory.Build.props rename to src/Directory.Build.props index cb42e3aa..220dbd97 100644 --- a/src/StellaOps.Feedser/Directory.Build.props +++ b/src/Directory.Build.props @@ -6,7 +6,7 @@ - + false runtime diff --git a/src/StellaOps.Feedser/Directory.Build.targets b/src/Directory.Build.targets similarity index 100% rename from src/StellaOps.Feedser/Directory.Build.targets rename to src/Directory.Build.targets diff --git a/src/__Libraries/StellaOps.DependencyInjection/IDependencyInjectionRoutine.cs b/src/StellaOps.DependencyInjection/IDependencyInjectionRoutine.cs similarity index 100% rename from src/__Libraries/StellaOps.DependencyInjection/IDependencyInjectionRoutine.cs rename to src/StellaOps.DependencyInjection/IDependencyInjectionRoutine.cs diff --git a/src/__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj b/src/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj similarity index 100% rename from src/__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj rename to src/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core.Tests/JobCoordinatorTests.cs b/src/StellaOps.Feedser.Core.Tests/JobCoordinatorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core.Tests/JobCoordinatorTests.cs rename to src/StellaOps.Feedser.Core.Tests/JobCoordinatorTests.cs diff --git a/src/StellaOps.Feedser.Core.Tests/JobPluginRegistrationExtensionsTests.cs b/src/StellaOps.Feedser.Core.Tests/JobPluginRegistrationExtensionsTests.cs new file mode 100644 index 00000000..0dc7fdae --- /dev/null +++ b/src/StellaOps.Feedser.Core.Tests/JobPluginRegistrationExtensionsTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using StellaOps.Feedser.Core.Jobs; +using StellaOps.Plugin.Hosting; + +namespace StellaOps.Feedser.Core.Tests; + +public sealed class JobPluginRegistrationExtensionsTests +{ + [Fact] + public void RegisterJobPluginRoutines_LoadsPluginsAndRegistersDefinitions() + { + var services = new ServiceCollection(); + services.AddJobScheduler(); + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["plugin:test:timeoutSeconds"] = "45", + }) + .Build(); + + var assemblyPath = typeof(JobPluginRegistrationExtensionsTests).Assembly.Location; + var pluginDirectory = Path.GetDirectoryName(assemblyPath)!; + var pluginFile = Path.GetFileName(assemblyPath); + + var options = new PluginHostOptions + { + BaseDirectory = pluginDirectory, + PluginsDirectory = pluginDirectory, + EnsureDirectoryExists = false, + RecursiveSearch = false, + }; + options.SearchPatterns.Add(pluginFile); + + services.RegisterJobPluginRoutines(configuration, options); + + Assert.Contains( + services, + descriptor => descriptor.ServiceType == typeof(PluginHostResult)); + + Assert.Contains( + services, + descriptor => descriptor.ServiceType.FullName == typeof(PluginRoutineExecuted).FullName); + + using var provider = services.BuildServiceProvider(); + var schedulerOptions = provider.GetRequiredService>().Value; + + Assert.True(schedulerOptions.Definitions.TryGetValue(PluginJob.JobKind, out var definition)); + Assert.NotNull(definition); + Assert.Equal(PluginJob.JobKind, definition.Kind); + Assert.Equal("StellaOps.Feedser.Core.Tests.PluginJob", definition.JobType.FullName); + Assert.Equal(TimeSpan.FromSeconds(45), definition.Timeout); + Assert.Equal(TimeSpan.FromSeconds(5), definition.LeaseDuration); + Assert.Equal("*/10 * * * *", definition.CronExpression); + } +} diff --git a/src/StellaOps.Feedser.Core.Tests/JobSchedulerBuilderTests.cs b/src/StellaOps.Feedser.Core.Tests/JobSchedulerBuilderTests.cs new file mode 100644 index 00000000..5e6f6385 --- /dev/null +++ b/src/StellaOps.Feedser.Core.Tests/JobSchedulerBuilderTests.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using StellaOps.Feedser.Core.Jobs; + +namespace StellaOps.Feedser.Core.Tests; + +public sealed class JobSchedulerBuilderTests +{ + [Fact] + public void AddJob_RegistersDefinitionWithExplicitMetadata() + { + var services = new ServiceCollection(); + var builder = services.AddJobScheduler(); + + builder.AddJob( + kind: "jobs:test", + cronExpression: "*/5 * * * *", + timeout: TimeSpan.FromMinutes(42), + leaseDuration: TimeSpan.FromMinutes(7), + enabled: false); + + using var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.True(options.Definitions.TryGetValue("jobs:test", out var definition)); + Assert.NotNull(definition); + Assert.Equal(typeof(TestJob), definition.JobType); + Assert.Equal(TimeSpan.FromMinutes(42), definition.Timeout); + Assert.Equal(TimeSpan.FromMinutes(7), definition.LeaseDuration); + Assert.Equal("*/5 * * * *", definition.CronExpression); + Assert.False(definition.Enabled); + } + + [Fact] + public void AddJob_UsesDefaults_WhenOptionalMetadataExcluded() + { + var services = new ServiceCollection(); + var builder = services.AddJobScheduler(options => + { + options.DefaultTimeout = TimeSpan.FromSeconds(123); + options.DefaultLeaseDuration = TimeSpan.FromSeconds(45); + }); + + builder.AddJob(kind: "jobs:defaults"); + + using var provider = services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + + Assert.True(options.Definitions.TryGetValue("jobs:defaults", out var definition)); + Assert.NotNull(definition); + Assert.Equal(typeof(DefaultedJob), definition.JobType); + Assert.Equal(TimeSpan.FromSeconds(123), definition.Timeout); + Assert.Equal(TimeSpan.FromSeconds(45), definition.LeaseDuration); + Assert.Null(definition.CronExpression); + Assert.True(definition.Enabled); + } + + private sealed class TestJob : IJob + { + public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken) + => Task.CompletedTask; + } + + private sealed class DefaultedJob : IJob + { + public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken) + => Task.CompletedTask; + } +} diff --git a/src/StellaOps.Feedser.Core.Tests/PluginRoutineFixtures.cs b/src/StellaOps.Feedser.Core.Tests/PluginRoutineFixtures.cs new file mode 100644 index 00000000..80744720 --- /dev/null +++ b/src/StellaOps.Feedser.Core.Tests/PluginRoutineFixtures.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using StellaOps.DependencyInjection; +using StellaOps.Feedser.Core.Jobs; + +namespace StellaOps.Feedser.Core.Tests; + +public sealed class TestPluginRoutine : IDependencyInjectionRoutine +{ + public IServiceCollection Register(IServiceCollection services, IConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(configuration); + + var builder = new JobSchedulerBuilder(services); + var timeoutSeconds = configuration.GetValue("plugin:test:timeoutSeconds") ?? 30; + + builder.AddJob( + PluginJob.JobKind, + cronExpression: "*/10 * * * *", + timeout: TimeSpan.FromSeconds(timeoutSeconds), + leaseDuration: TimeSpan.FromSeconds(5)); + + services.AddSingleton(); + return services; + } +} + +public sealed class PluginRoutineExecuted +{ +} + +public sealed class PluginJob : IJob +{ + public const string JobKind = "plugin:test"; + + public Task ExecuteAsync(JobExecutionContext context, CancellationToken cancellationToken) + => Task.CompletedTask; +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core.Tests/StellaOps.Feedser.Core.Tests.csproj b/src/StellaOps.Feedser.Core.Tests/StellaOps.Feedser.Core.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core.Tests/StellaOps.Feedser.Core.Tests.csproj rename to src/StellaOps.Feedser.Core.Tests/StellaOps.Feedser.Core.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/AGENTS.md b/src/StellaOps.Feedser.Core/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/AGENTS.md rename to src/StellaOps.Feedser.Core/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJob.cs b/src/StellaOps.Feedser.Core/Jobs/IJob.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJob.cs rename to src/StellaOps.Feedser.Core/Jobs/IJob.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJobCoordinator.cs b/src/StellaOps.Feedser.Core/Jobs/IJobCoordinator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJobCoordinator.cs rename to src/StellaOps.Feedser.Core/Jobs/IJobCoordinator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJobStore.cs b/src/StellaOps.Feedser.Core/Jobs/IJobStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/IJobStore.cs rename to src/StellaOps.Feedser.Core/Jobs/IJobStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/ILeaseStore.cs b/src/StellaOps.Feedser.Core/Jobs/ILeaseStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/ILeaseStore.cs rename to src/StellaOps.Feedser.Core/Jobs/ILeaseStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobCoordinator.cs b/src/StellaOps.Feedser.Core/Jobs/JobCoordinator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobCoordinator.cs rename to src/StellaOps.Feedser.Core/Jobs/JobCoordinator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobDefinition.cs b/src/StellaOps.Feedser.Core/Jobs/JobDefinition.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobDefinition.cs rename to src/StellaOps.Feedser.Core/Jobs/JobDefinition.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobDiagnostics.cs b/src/StellaOps.Feedser.Core/Jobs/JobDiagnostics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobDiagnostics.cs rename to src/StellaOps.Feedser.Core/Jobs/JobDiagnostics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobExecutionContext.cs b/src/StellaOps.Feedser.Core/Jobs/JobExecutionContext.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobExecutionContext.cs rename to src/StellaOps.Feedser.Core/Jobs/JobExecutionContext.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobLease.cs b/src/StellaOps.Feedser.Core/Jobs/JobLease.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobLease.cs rename to src/StellaOps.Feedser.Core/Jobs/JobLease.cs diff --git a/src/StellaOps.Feedser.Core/Jobs/JobPluginRegistrationExtensions.cs b/src/StellaOps.Feedser.Core/Jobs/JobPluginRegistrationExtensions.cs new file mode 100644 index 00000000..47baee39 --- /dev/null +++ b/src/StellaOps.Feedser.Core/Jobs/JobPluginRegistrationExtensions.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using StellaOps.DependencyInjection; +using StellaOps.Plugin.Hosting; + +namespace StellaOps.Feedser.Core.Jobs; + +public static class JobPluginRegistrationExtensions +{ + public static IServiceCollection RegisterJobPluginRoutines( + this IServiceCollection services, + IConfiguration configuration, + PluginHostOptions options, + ILogger? logger = null) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(configuration); + ArgumentNullException.ThrowIfNull(options); + + var loadResult = PluginHost.LoadPlugins(options, logger); + + if (!services.Any(sd => sd.ServiceType == typeof(PluginHostResult))) + { + services.AddSingleton(loadResult); + } + + var currentServices = services; + var seenRoutineTypes = new HashSet(StringComparer.Ordinal); + + foreach (var plugin in loadResult.Plugins) + { + foreach (var routineType in GetRoutineTypes(plugin.Assembly)) + { + if (!typeof(IDependencyInjectionRoutine).IsAssignableFrom(routineType)) + { + continue; + } + + if (routineType.IsInterface || routineType.IsAbstract) + { + continue; + } + + var routineKey = routineType.FullName ?? routineType.Name; + if (!seenRoutineTypes.Add(routineKey)) + { + continue; + } + + IDependencyInjectionRoutine? routineInstance; + try + { + routineInstance = Activator.CreateInstance(routineType) as IDependencyInjectionRoutine; + } + catch (Exception ex) + { + logger?.LogWarning( + ex, + "Failed to create dependency injection routine {Routine} from plugin {Plugin}.", + routineType.FullName ?? routineType.Name, + plugin.Assembly.FullName ?? plugin.AssemblyPath); + continue; + } + + if (routineInstance is null) + { + continue; + } + + try + { + var updated = routineInstance.Register(currentServices, configuration); + if (updated is not null && !ReferenceEquals(updated, currentServices)) + { + currentServices = updated; + } + } + catch (Exception ex) + { + logger?.LogError( + ex, + "Dependency injection routine {Routine} from plugin {Plugin} threw during registration.", + routineType.FullName ?? routineType.Name, + plugin.Assembly.FullName ?? plugin.AssemblyPath); + } + } + } + + if (loadResult.MissingOrderedPlugins.Count > 0) + { + logger?.LogWarning( + "Missing ordered plugin(s): {Missing}", + string.Join(", ", loadResult.MissingOrderedPlugins)); + } + + return currentServices; + } + + private static IEnumerable GetRoutineTypes(Assembly assembly) + { + if (assembly is null) + { + yield break; + } + + Type[] types; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + types = ex.Types.Where(static t => t is not null)! + .Select(static t => t!) + .ToArray(); + } + + foreach (var type in types) + { + yield return type; + } + } +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunCompletion.cs b/src/StellaOps.Feedser.Core/Jobs/JobRunCompletion.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunCompletion.cs rename to src/StellaOps.Feedser.Core/Jobs/JobRunCompletion.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunCreateRequest.cs b/src/StellaOps.Feedser.Core/Jobs/JobRunCreateRequest.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunCreateRequest.cs rename to src/StellaOps.Feedser.Core/Jobs/JobRunCreateRequest.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunSnapshot.cs b/src/StellaOps.Feedser.Core/Jobs/JobRunSnapshot.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunSnapshot.cs rename to src/StellaOps.Feedser.Core/Jobs/JobRunSnapshot.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunStatus.cs b/src/StellaOps.Feedser.Core/Jobs/JobRunStatus.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobRunStatus.cs rename to src/StellaOps.Feedser.Core/Jobs/JobRunStatus.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerBuilder.cs b/src/StellaOps.Feedser.Core/Jobs/JobSchedulerBuilder.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerBuilder.cs rename to src/StellaOps.Feedser.Core/Jobs/JobSchedulerBuilder.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerHostedService.cs b/src/StellaOps.Feedser.Core/Jobs/JobSchedulerHostedService.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerHostedService.cs rename to src/StellaOps.Feedser.Core/Jobs/JobSchedulerHostedService.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerOptions.cs b/src/StellaOps.Feedser.Core/Jobs/JobSchedulerOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobSchedulerOptions.cs rename to src/StellaOps.Feedser.Core/Jobs/JobSchedulerOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobTriggerResult.cs b/src/StellaOps.Feedser.Core/Jobs/JobTriggerResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/JobTriggerResult.cs rename to src/StellaOps.Feedser.Core/Jobs/JobTriggerResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/ServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Core/Jobs/ServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/Jobs/ServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Core/Jobs/ServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj b/src/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj similarity index 90% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj rename to src/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj index 3a419624..0655c8f3 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj +++ b/src/StellaOps.Feedser.Core/StellaOps.Feedser.Core.csproj @@ -14,5 +14,6 @@ + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Core/TASKS.md b/src/StellaOps.Feedser.Core/TASKS.md similarity index 84% rename from src/StellaOps.Feedser/StellaOps.Feedser.Core/TASKS.md rename to src/StellaOps.Feedser.Core/TASKS.md index eb7d754f..0712e0a7 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Core/TASKS.md +++ b/src/StellaOps.Feedser.Core/TASKS.md @@ -8,7 +8,7 @@ |Run telemetry enrichment|BE-Core|Observability|DONE – `JobDiagnostics` ties activities & counters into coordinator/scheduler paths.| |Deterministic params hashing|BE-Core|Core|DONE – `JobParametersHasher` creates SHA256 hash.| |Golden tests for timeout/cancel|QA|Core|DONE – JobCoordinatorTests cover cancellation timeout path.| -|JobSchedulerBuilder options registry coverage|BE-Core|Core|TODO – verify cron/timeout/lease metadata persists for scheduler surfaces.| -|Plugin discovery + DI glue with PluginHost|BE-Core|Plugin libs|TODO – auto-register job routines for connectors/exporters.| +|JobSchedulerBuilder options registry coverage|BE-Core|Core|DONE – added scheduler tests confirming cron/timeout/lease metadata persists via JobSchedulerOptions.| +|Plugin discovery + DI glue with PluginHost|BE-Core|Plugin libs|DONE – JobPluginRegistrationExtensions now loads PluginHost routines and wires connector/exporter registrations.| |Harden lease release error handling in JobCoordinator|BE-Core|Storage.Mongo|DONE – lease release failures now logged, wrapped, and drive run failure status; fire-and-forget execution guarded. Verified with `dotnet test --no-build --filter JobCoordinator`.| |Validate job trigger parameters for serialization|BE-Core|WebService|DONE – trigger parameters normalized/serialized with defensive checks returning InvalidParameters on failure. Full-suite `dotnet test --no-build` currently red from live connector fixture drift (Oracle/JVN/RedHat).| diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExportSnapshotBuilderTests.cs b/src/StellaOps.Feedser.Exporter.Json.Tests/JsonExportSnapshotBuilderTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExportSnapshotBuilderTests.cs rename to src/StellaOps.Feedser.Exporter.Json.Tests/JsonExportSnapshotBuilderTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterDependencyInjectionRoutineTests.cs b/src/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterDependencyInjectionRoutineTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterDependencyInjectionRoutineTests.cs rename to src/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterDependencyInjectionRoutineTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterParitySmokeTests.cs b/src/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterParitySmokeTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterParitySmokeTests.cs rename to src/StellaOps.Feedser.Exporter.Json.Tests/JsonExporterParitySmokeTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonFeedExporterTests.cs b/src/StellaOps.Feedser.Exporter.Json.Tests/JsonFeedExporterTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/JsonFeedExporterTests.cs rename to src/StellaOps.Feedser.Exporter.Json.Tests/JsonFeedExporterTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/StellaOps.Feedser.Exporter.Json.Tests.csproj b/src/StellaOps.Feedser.Exporter.Json.Tests/StellaOps.Feedser.Exporter.Json.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/StellaOps.Feedser.Exporter.Json.Tests.csproj rename to src/StellaOps.Feedser.Exporter.Json.Tests/StellaOps.Feedser.Exporter.Json.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/VulnListJsonExportPathResolverTests.cs b/src/StellaOps.Feedser.Exporter.Json.Tests/VulnListJsonExportPathResolverTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json.Tests/VulnListJsonExportPathResolverTests.cs rename to src/StellaOps.Feedser.Exporter.Json.Tests/VulnListJsonExportPathResolverTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/AGENTS.md b/src/StellaOps.Feedser.Exporter.Json/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/AGENTS.md rename to src/StellaOps.Feedser.Exporter.Json/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/ExportDigestCalculator.cs b/src/StellaOps.Feedser.Exporter.Json/ExportDigestCalculator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/ExportDigestCalculator.cs rename to src/StellaOps.Feedser.Exporter.Json/ExportDigestCalculator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/ExporterVersion.cs b/src/StellaOps.Feedser.Exporter.Json/ExporterVersion.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/ExporterVersion.cs rename to src/StellaOps.Feedser.Exporter.Json/ExporterVersion.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/IJsonExportPathResolver.cs b/src/StellaOps.Feedser.Exporter.Json/IJsonExportPathResolver.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/IJsonExportPathResolver.cs rename to src/StellaOps.Feedser.Exporter.Json/IJsonExportPathResolver.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportFile.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportFile.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportFile.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportFile.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportJob.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportJob.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportJob.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportJob.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportManifestWriter.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportManifestWriter.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportManifestWriter.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportManifestWriter.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportOptions.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportOptions.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportResult.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportResult.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportSnapshotBuilder.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExportSnapshotBuilder.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExportSnapshotBuilder.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExportSnapshotBuilder.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExporterDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExporterDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExporterDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExporterDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExporterPlugin.cs b/src/StellaOps.Feedser.Exporter.Json/JsonExporterPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonExporterPlugin.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonExporterPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonFeedExporter.cs b/src/StellaOps.Feedser.Exporter.Json/JsonFeedExporter.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/JsonFeedExporter.cs rename to src/StellaOps.Feedser.Exporter.Json/JsonFeedExporter.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj b/src/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj rename to src/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj index aa5bda25..565cd3dd 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj +++ b/src/StellaOps.Feedser.Exporter.Json/StellaOps.Feedser.Exporter.Json.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/TASKS.md b/src/StellaOps.Feedser.Exporter.Json/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/TASKS.md rename to src/StellaOps.Feedser.Exporter.Json/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/VulnListJsonExportPathResolver.cs b/src/StellaOps.Feedser.Exporter.Json/VulnListJsonExportPathResolver.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.Json/VulnListJsonExportPathResolver.cs rename to src/StellaOps.Feedser.Exporter.Json/VulnListJsonExportPathResolver.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj rename to src/StellaOps.Feedser.Exporter.TrivyDb.Tests/StellaOps.Feedser.Exporter.TrivyDb.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbExportPlannerTests.cs b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbExportPlannerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbExportPlannerTests.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbExportPlannerTests.cs diff --git a/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs new file mode 100644 index 00000000..5bf0aa61 --- /dev/null +++ b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs @@ -0,0 +1,589 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using StellaOps.Feedser.Exporter.Json; +using StellaOps.Feedser.Exporter.TrivyDb; +using StellaOps.Feedser.Models; +using StellaOps.Feedser.Storage.Mongo.Advisories; +using StellaOps.Feedser.Storage.Mongo.Exporting; + +namespace StellaOps.Feedser.Exporter.TrivyDb.Tests; + +public sealed class TrivyDbFeedExporterTests : IDisposable +{ + private readonly string _root; + private readonly string _jsonRoot; + + public TrivyDbFeedExporterTests() + { + _root = Directory.CreateTempSubdirectory("feedser-trivy-exporter-tests").FullName; + _jsonRoot = Path.Combine(_root, "tree"); + } + + [Fact] + public async Task ExportAsync_SortsAdvisoriesByKeyDeterministically() + { + var advisoryB = CreateSampleAdvisory("CVE-2024-1002", "Second advisory"); + var advisoryA = CreateSampleAdvisory("CVE-2024-1001", "First advisory"); + + var advisoryStore = new StubAdvisoryStore(advisoryB, advisoryA); + + var optionsValue = new TrivyDbExportOptions + { + OutputRoot = _root, + ReferencePrefix = "example/trivy", + KeepWorkingTree = false, + Json = new JsonExportOptions + { + OutputRoot = _jsonRoot, + MaintainLatestSymlink = false, + }, + }; + + var options = Options.Create(optionsValue); + var packageBuilder = new TrivyDbPackageBuilder(); + var ociWriter = new TrivyDbOciWriter(); + var planner = new TrivyDbExportPlanner(); + var stateStore = new InMemoryExportStateStore(); + var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-09-20T00:00:00Z", CultureInfo.InvariantCulture)); + var stateManager = new ExportStateManager(stateStore, timeProvider); + var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new + { + Version = 2, + NextUpdate = "2024-09-21T00:00:00Z", + UpdatedAt = "2024-09-20T00:00:00Z", + }); + + var recordingBuilder = new RecordingTrivyDbBuilder(_root, builderMetadata); + var orasPusher = new StubTrivyDbOrasPusher(); + var exporter = new TrivyDbFeedExporter( + advisoryStore, + new VulnListJsonExportPathResolver(), + options, + packageBuilder, + ociWriter, + stateManager, + planner, + recordingBuilder, + orasPusher, + NullLogger.Instance, + timeProvider); + + using var provider = new ServiceCollection().BuildServiceProvider(); + await exporter.ExportAsync(provider, CancellationToken.None); + + var paths = recordingBuilder.LastRelativePaths; + Assert.NotNull(paths); + + var sorted = paths!.OrderBy(static p => p, StringComparer.Ordinal).ToArray(); + Assert.Equal(sorted, paths); + + advisoryStore.SetAdvisories(advisoryA, advisoryB); + timeProvider.Advance(TimeSpan.FromMinutes(7)); + await exporter.ExportAsync(provider, CancellationToken.None); + + var record = await stateStore.FindAsync(TrivyDbFeedExporter.ExporterId, CancellationToken.None); + Assert.NotNull(record); + Assert.Equal("20240920T000000Z", record!.BaseExportId); + Assert.Single(recordingBuilder.ManifestDigests); + } + + [Fact] + public async Task ExportAsync_SmallDatasetProducesDeterministicOciLayout() + { + var advisories = new[] + { + CreateSampleAdvisory("CVE-2024-3000", "Demo advisory 1"), + CreateSampleAdvisory("CVE-2024-3001", "Demo advisory 2"), + }; + + var run1 = await RunDeterministicExportAsync(advisories); + var run2 = await RunDeterministicExportAsync(advisories); + + Assert.Equal(run1.ManifestDigest, run2.ManifestDigest); + Assert.Equal(run1.IndexJson, run2.IndexJson); + Assert.Equal(run1.MetadataJson, run2.MetadataJson); + Assert.Equal(run1.ManifestJson, run2.ManifestJson); + + var digests1 = run1.Blobs.Keys.OrderBy(static d => d, StringComparer.Ordinal).ToArray(); + var digests2 = run2.Blobs.Keys.OrderBy(static d => d, StringComparer.Ordinal).ToArray(); + Assert.Equal(digests1, digests2); + + foreach (var digest in digests1) + { + Assert.True(run2.Blobs.TryGetValue(digest, out var other), $"Missing digest {digest} in second run"); + Assert.True(run1.Blobs[digest].SequenceEqual(other), $"Blob {digest} differs between runs"); + } + + using var metadataDoc = JsonDocument.Parse(run1.MetadataJson); + Assert.Equal(2, metadataDoc.RootElement.GetProperty("advisoryCount").GetInt32()); + + using var manifestDoc = JsonDocument.Parse(run1.ManifestJson); + Assert.Equal(TrivyDbMediaTypes.TrivyConfig, manifestDoc.RootElement.GetProperty("config").GetProperty("mediaType").GetString()); + var layer = manifestDoc.RootElement.GetProperty("layers")[0]; + Assert.Equal(TrivyDbMediaTypes.TrivyLayer, layer.GetProperty("mediaType").GetString()); + } + + [Fact] + public void ExportOptions_GetExportRoot_NormalizesRelativeRoot() + { + var options = new TrivyDbExportOptions + { + OutputRoot = Path.Combine("..", "exports", "trivy-test"), + }; + + var exportId = "20240901T000000Z"; + var path = options.GetExportRoot(exportId); + + Assert.True(Path.IsPathRooted(path)); + Assert.EndsWith(Path.Combine("exports", "trivy-test", exportId), path, StringComparison.Ordinal); + } + + [Fact] + public async Task ExportAsync_PersistsStateAndSkipsWhenDigestUnchanged() + { + var advisory = CreateSampleAdvisory(); + var advisoryStore = new StubAdvisoryStore(advisory); + + var optionsValue = new TrivyDbExportOptions + { + OutputRoot = _root, + ReferencePrefix = "example/trivy", + Json = new JsonExportOptions + { + OutputRoot = _jsonRoot, + MaintainLatestSymlink = false, + }, + KeepWorkingTree = false, + }; + + var options = Options.Create(optionsValue); + var packageBuilder = new TrivyDbPackageBuilder(); + var ociWriter = new TrivyDbOciWriter(); + var planner = new TrivyDbExportPlanner(); + var stateStore = new InMemoryExportStateStore(); + var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-09-01T00:00:00Z", CultureInfo.InvariantCulture)); + var stateManager = new ExportStateManager(stateStore, timeProvider); + var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new + { + Version = 2, + NextUpdate = "2024-09-02T00:00:00Z", + UpdatedAt = "2024-09-01T00:00:00Z", + }); + var builder = new StubTrivyDbBuilder(_root, builderMetadata); + var orasPusher = new StubTrivyDbOrasPusher(); + var exporter = new TrivyDbFeedExporter( + advisoryStore, + new VulnListJsonExportPathResolver(), + options, + packageBuilder, + ociWriter, + stateManager, + planner, + builder, + orasPusher, + NullLogger.Instance, + timeProvider); + + using var provider = new ServiceCollection().BuildServiceProvider(); + await exporter.ExportAsync(provider, CancellationToken.None); + + var record = await stateStore.FindAsync(TrivyDbFeedExporter.ExporterId, CancellationToken.None); + Assert.NotNull(record); + Assert.Equal("20240901T000000Z", record!.BaseExportId); + Assert.False(string.IsNullOrEmpty(record.ExportCursor)); + + var baseExportId = record.BaseExportId ?? string.Empty; + Assert.False(string.IsNullOrEmpty(baseExportId)); + var firstExportDirectory = Path.Combine(_root, baseExportId); + Assert.True(Directory.Exists(firstExportDirectory)); + + timeProvider.Advance(TimeSpan.FromMinutes(5)); + await exporter.ExportAsync(provider, CancellationToken.None); + + var updatedRecord = await stateStore.FindAsync(TrivyDbFeedExporter.ExporterId, CancellationToken.None); + Assert.NotNull(updatedRecord); + Assert.Equal(record.UpdatedAt, updatedRecord!.UpdatedAt); + Assert.Equal(record.LastFullDigest, updatedRecord.LastFullDigest); + + var skippedExportDirectory = Path.Combine(_root, "20240901T000500Z"); + Assert.False(Directory.Exists(skippedExportDirectory)); + + Assert.Empty(orasPusher.Pushes); + } + + [Fact] + public async Task ExportAsync_CreatesOfflineBundle() + { + var advisory = CreateSampleAdvisory(); + var advisoryStore = new StubAdvisoryStore(advisory); + + var optionsValue = new TrivyDbExportOptions + { + OutputRoot = _root, + ReferencePrefix = "example/trivy", + Json = new JsonExportOptions + { + OutputRoot = _jsonRoot, + MaintainLatestSymlink = false, + }, + KeepWorkingTree = false, + OfflineBundle = new TrivyDbOfflineBundleOptions + { + Enabled = true, + FileName = "{exportId}.bundle.tar.gz", + }, + }; + + var options = Options.Create(optionsValue); + var packageBuilder = new TrivyDbPackageBuilder(); + var ociWriter = new TrivyDbOciWriter(); + var planner = new TrivyDbExportPlanner(); + var stateStore = new InMemoryExportStateStore(); + var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-09-15T00:00:00Z", CultureInfo.InvariantCulture)); + var stateManager = new ExportStateManager(stateStore, timeProvider); + var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new + { + Version = 2, + NextUpdate = "2024-09-16T00:00:00Z", + UpdatedAt = "2024-09-15T00:00:00Z", + }); + var builder = new StubTrivyDbBuilder(_root, builderMetadata); + var orasPusher = new StubTrivyDbOrasPusher(); + var exporter = new TrivyDbFeedExporter( + advisoryStore, + new VulnListJsonExportPathResolver(), + options, + packageBuilder, + ociWriter, + stateManager, + planner, + builder, + orasPusher, + NullLogger.Instance, + timeProvider); + + using var provider = new ServiceCollection().BuildServiceProvider(); + await exporter.ExportAsync(provider, CancellationToken.None); + + var exportId = "20240915T000000Z"; + var bundlePath = Path.Combine(_root, $"{exportId}.bundle.tar.gz"); + Assert.True(File.Exists(bundlePath)); + Assert.Empty(orasPusher.Pushes); + } + + private static Advisory CreateSampleAdvisory( + string advisoryKey = "CVE-2024-9999", + string title = "Trivy Export Test") + { + return new Advisory( + advisoryKey: advisoryKey, + title: title, + summary: null, + language: "en", + published: DateTimeOffset.Parse("2024-08-01T00:00:00Z", CultureInfo.InvariantCulture), + modified: DateTimeOffset.Parse("2024-08-02T00:00:00Z", CultureInfo.InvariantCulture), + severity: "medium", + exploitKnown: false, + aliases: new[] { "CVE-2024-9999" }, + references: Array.Empty(), + affectedPackages: Array.Empty(), + cvssMetrics: Array.Empty(), + provenance: Array.Empty()); + } + + public void Dispose() + { + try + { + if (Directory.Exists(_root)) + { + Directory.Delete(_root, recursive: true); + } + } + catch + { + // best effort cleanup + } + } + + private sealed class StubAdvisoryStore : IAdvisoryStore + { + private IReadOnlyList _advisories; + + public StubAdvisoryStore(params Advisory[] advisories) + { + _advisories = advisories; + } + + public void SetAdvisories(params Advisory[] advisories) + { + _advisories = advisories; + } + + public Task> GetRecentAsync(int limit, CancellationToken cancellationToken) + => Task.FromResult(_advisories); + + public Task FindAsync(string advisoryKey, CancellationToken cancellationToken) + => Task.FromResult(_advisories.FirstOrDefault(a => a.AdvisoryKey == advisoryKey)); + + public Task UpsertAsync(Advisory advisory, CancellationToken cancellationToken) + => Task.CompletedTask; + + public IAsyncEnumerable StreamAsync(CancellationToken cancellationToken) + { + return EnumerateAsync(cancellationToken); + + async IAsyncEnumerable EnumerateAsync([EnumeratorCancellation] CancellationToken ct) + { + foreach (var advisory in _advisories) + { + ct.ThrowIfCancellationRequested(); + yield return advisory; + await Task.Yield(); + } + } + } + } + + private sealed class InMemoryExportStateStore : IExportStateStore + { + private ExportStateRecord? _record; + + public Task FindAsync(string id, CancellationToken cancellationToken) + => Task.FromResult(_record); + + public Task UpsertAsync(ExportStateRecord record, CancellationToken cancellationToken) + { + _record = record; + return Task.FromResult(record); + } + } + + private sealed class TestTimeProvider : TimeProvider + { + private DateTimeOffset _now; + + public TestTimeProvider(DateTimeOffset start) => _now = start; + + public override DateTimeOffset GetUtcNow() => _now; + + public void Advance(TimeSpan delta) => _now = _now.Add(delta); + } + + private sealed class StubTrivyDbBuilder : ITrivyDbBuilder + { + private readonly string _root; + private readonly byte[] _metadata; + + public StubTrivyDbBuilder(string root, byte[] metadata) + { + _root = root; + _metadata = metadata; + } + + public Task BuildAsync( + JsonExportResult jsonTree, + DateTimeOffset exportedAt, + string exportId, + CancellationToken cancellationToken) + { + var workingDirectory = Directory.CreateDirectory(Path.Combine(_root, $"builder-{exportId}")).FullName; + var archivePath = Path.Combine(workingDirectory, "db.tar.gz"); + var payload = new byte[] { 0x1, 0x2, 0x3, 0x4 }; + File.WriteAllBytes(archivePath, payload); + using var sha256 = SHA256.Create(); + var digest = "sha256:" + Convert.ToHexString(sha256.ComputeHash(payload)).ToLowerInvariant(); + var length = payload.Length; + + return Task.FromResult(new TrivyDbBuilderResult( + archivePath, + digest, + length, + _metadata, + workingDirectory)); + } + } + + private sealed class RecordingTrivyDbBuilder : ITrivyDbBuilder + { + private readonly string _root; + private readonly byte[] _metadata; + private readonly List _manifestDigests = new(); + + public RecordingTrivyDbBuilder(string root, byte[] metadata) + { + _root = root; + _metadata = metadata; + } + + public IReadOnlyList ManifestDigests => _manifestDigests; + public string[]? LastRelativePaths { get; private set; } + + public Task BuildAsync( + JsonExportResult jsonTree, + DateTimeOffset exportedAt, + string exportId, + CancellationToken cancellationToken) + { + LastRelativePaths = jsonTree.Files.Select(static file => file.RelativePath).ToArray(); + + var workingDirectory = Directory.CreateDirectory(Path.Combine(_root, $"builder-{exportId}")).FullName; + var archivePath = Path.Combine(workingDirectory, "db.tar.gz"); + var payload = new byte[] { 0x5, 0x6, 0x7, 0x8 }; + File.WriteAllBytes(archivePath, payload); + using var sha256 = SHA256.Create(); + var digest = "sha256:" + Convert.ToHexString(sha256.ComputeHash(payload)).ToLowerInvariant(); + _manifestDigests.Add(digest); + + return Task.FromResult(new TrivyDbBuilderResult( + archivePath, + digest, + payload.Length, + _metadata, + workingDirectory)); + } + } + + private sealed record RunArtifacts( + string ExportId, + string ManifestDigest, + string IndexJson, + string MetadataJson, + string ManifestJson, + IReadOnlyDictionary Blobs); + + private async Task RunDeterministicExportAsync(IReadOnlyList advisories) + { + var workspace = Path.Combine(_root, $"deterministic-{Guid.NewGuid():N}"); + var jsonRoot = Path.Combine(workspace, "tree"); + Directory.CreateDirectory(workspace); + + var advisoryStore = new StubAdvisoryStore(advisories.ToArray()); + + var optionsValue = new TrivyDbExportOptions + { + OutputRoot = workspace, + ReferencePrefix = "example/trivy", + KeepWorkingTree = true, + Json = new JsonExportOptions + { + OutputRoot = jsonRoot, + MaintainLatestSymlink = false, + }, + }; + + var exportedAt = DateTimeOffset.Parse("2024-10-01T00:00:00Z", CultureInfo.InvariantCulture); + var options = Options.Create(optionsValue); + var packageBuilder = new TrivyDbPackageBuilder(); + var ociWriter = new TrivyDbOciWriter(); + var planner = new TrivyDbExportPlanner(); + var stateStore = new InMemoryExportStateStore(); + var timeProvider = new TestTimeProvider(exportedAt); + var stateManager = new ExportStateManager(stateStore, timeProvider); + var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new + { + Version = 2, + NextUpdate = "2024-10-02T00:00:00Z", + UpdatedAt = "2024-10-01T00:00:00Z", + }); + + var builder = new DeterministicTrivyDbBuilder(workspace, builderMetadata); + var orasPusher = new StubTrivyDbOrasPusher(); + var exporter = new TrivyDbFeedExporter( + advisoryStore, + new VulnListJsonExportPathResolver(), + options, + packageBuilder, + ociWriter, + stateManager, + planner, + builder, + orasPusher, + NullLogger.Instance, + timeProvider); + + using var provider = new ServiceCollection().BuildServiceProvider(); + await exporter.ExportAsync(provider, CancellationToken.None); + + var exportId = exportedAt.ToString(optionsValue.TagFormat, CultureInfo.InvariantCulture); + var layoutPath = Path.Combine(workspace, exportId); + + var indexJson = await File.ReadAllTextAsync(Path.Combine(layoutPath, "index.json"), Encoding.UTF8); + var metadataJson = await File.ReadAllTextAsync(Path.Combine(layoutPath, "metadata.json"), Encoding.UTF8); + + using var indexDoc = JsonDocument.Parse(indexJson); + var manifestNode = indexDoc.RootElement.GetProperty("manifests")[0]; + var manifestDigest = manifestNode.GetProperty("digest").GetString()!; + + var manifestHex = manifestDigest[7..]; + var manifestJson = await File.ReadAllTextAsync(Path.Combine(layoutPath, "blobs", "sha256", manifestHex), Encoding.UTF8); + + var blobs = new Dictionary(StringComparer.Ordinal); + var blobsRoot = Path.Combine(layoutPath, "blobs", "sha256"); + foreach (var file in Directory.GetFiles(blobsRoot)) + { + var name = Path.GetFileName(file); + var content = await File.ReadAllBytesAsync(file); + blobs[name] = content; + } + + Directory.Delete(workspace, recursive: true); + + return new RunArtifacts(exportId, manifestDigest, indexJson, metadataJson, manifestJson, blobs); + } + + private sealed class DeterministicTrivyDbBuilder : ITrivyDbBuilder + { + private readonly string _root; + private readonly byte[] _metadata; + private readonly byte[] _payload; + + public DeterministicTrivyDbBuilder(string root, byte[] metadata) + { + _root = root; + _metadata = metadata; + _payload = new byte[] { 0x21, 0x22, 0x23, 0x24, 0x25 }; + } + + public Task BuildAsync( + JsonExportResult jsonTree, + DateTimeOffset exportedAt, + string exportId, + CancellationToken cancellationToken) + { + var workingDirectory = Directory.CreateDirectory(Path.Combine(_root, $"builder-{exportId}")).FullName; + var archivePath = Path.Combine(workingDirectory, "db.tar.gz"); + File.WriteAllBytes(archivePath, _payload); + using var sha256 = SHA256.Create(); + var digest = "sha256:" + Convert.ToHexString(sha256.ComputeHash(_payload)).ToLowerInvariant(); + + return Task.FromResult(new TrivyDbBuilderResult( + archivePath, + digest, + _payload.Length, + _metadata, + workingDirectory)); + } + } + + private sealed class StubTrivyDbOrasPusher : ITrivyDbOrasPusher + { + public List<(string Layout, string Reference, string ExportId)> Pushes { get; } = new(); + + public Task PushAsync(string layoutPath, string reference, string exportId, CancellationToken cancellationToken) + { + Pushes.Add((layoutPath, reference, exportId)); + return Task.CompletedTask; + } + } +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbOciWriterTests.cs b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbOciWriterTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbOciWriterTests.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbOciWriterTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbPackageBuilderTests.cs b/src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbPackageBuilderTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbPackageBuilderTests.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbPackageBuilderTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/AGENTS.md b/src/StellaOps.Feedser.Exporter.TrivyDb/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/AGENTS.md rename to src/StellaOps.Feedser.Exporter.TrivyDb/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbBuilder.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbBuilder.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbBuilder.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbBuilder.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbOrasPusher.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbOrasPusher.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbOrasPusher.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/ITrivyDbOrasPusher.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciDescriptor.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/OciDescriptor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciDescriptor.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/OciDescriptor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciIndex.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/OciIndex.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciIndex.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/OciIndex.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciManifest.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/OciManifest.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/OciManifest.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/OciManifest.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj b/src/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj rename to src/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj index da5d299b..fe28b621 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/StellaOps.Feedser.Exporter.TrivyDb.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md b/src/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md similarity index 71% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md rename to src/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md index 95b3e2f6..4f7a7c4e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/TASKS.md @@ -6,8 +6,8 @@ |Pack db.tar.gz + metadata.json|BE-Export|Exporters|DONE – Builder output re-packed with fixed timestamps and zeroed gzip mtime.| |ORAS push support|BE-Export|Exporters|DONE – Optional `TrivyDbOrasPusher` shells `oras cp --from-oci-layout` with configurable args/env.| |Offline bundle toggle|BE-Export|Exporters|DONE – Deterministic OCI layout bundle emitted when enabled.| -|Deterministic ordering of advisories|BE-Export|Models|TODO – Sort by advisoryKey; stable array orders.| -|End-to-end tests with small dataset|QA|Exporters|TODO – Assert media types and reproducible digests across runs.| +|Deterministic ordering of advisories|BE-Export|Models|DONE – exporter now loads advisories, sorts by advisoryKey, and emits sorted JSON trees with deterministic OCI payloads.| +|End-to-end tests with small dataset|QA|Exporters|DONE – added deterministic round-trip test covering OCI layout, media types, and digest stability w/ repeated inputs.| |ExportState persistence & idempotence|BE-Export|Storage.Mongo|DOING – `ExportStateManager` keeps stable base export metadata; delta reset remains pending.| -|Streamed package building to avoid large copies|BE-Export|Exporters|TODO – refactor package writer to stream without double-buffering metadata/archive payloads.| +|Streamed package building to avoid large copies|BE-Export|Exporters|DONE – metadata/config now reuse backing arrays and OCI writer streams directly without double buffering.| |Plan incremental/delta exports|BE-Export|Exporters|TODO – design reuse of existing blobs/layers when inputs unchanged instead of rewriting full trees each run.| diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyConfigDocument.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyConfigDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyConfigDocument.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyConfigDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs similarity index 74% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs index 4d0e5b32..2ff58e60 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBlob.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -32,9 +33,12 @@ public sealed class TrivyDbBlob return new TrivyDbBlob(static _ => ValueTask.FromResult(Stream.Null), 0); } - return new TrivyDbBlob( - cancellationToken => ValueTask.FromResult(new MemoryStream(payload.ToArray(), writable: false)), - payload.Length); + if (MemoryMarshal.TryGetArray(payload, out ArraySegment segment) && segment.Array is not null && segment.Offset == 0) + { + return FromArray(segment.Array); + } + + return FromArray(payload.ToArray()); } public static TrivyDbBlob FromFile(string path, long length) @@ -59,4 +63,16 @@ public sealed class TrivyDbBlob options: FileOptions.Asynchronous | FileOptions.SequentialScan)), length); } + + public static TrivyDbBlob FromArray(byte[] buffer) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + return new TrivyDbBlob( + _ => ValueTask.FromResult(new MemoryStream(buffer, writable: false)), + buffer.LongLength); + } } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBoltBuilder.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBoltBuilder.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBoltBuilder.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBoltBuilder.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBuilderResult.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBuilderResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBuilderResult.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbBuilderResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportJob.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportJob.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportJob.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportJob.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportMode.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportMode.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportMode.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportMode.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportOptions.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportOptions.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlan.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlan.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlan.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlan.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlanner.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlanner.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlanner.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExportPlanner.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterPlugin.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterPlugin.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbExporterPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs similarity index 94% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs index ed0864da..773b72c8 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbFeedExporter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.IO.Compression; @@ -12,6 +13,7 @@ using System.Formats.Tar; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using StellaOps.Feedser.Exporter.Json; +using StellaOps.Feedser.Models; using StellaOps.Feedser.Storage.Mongo.Advisories; using StellaOps.Feedser.Storage.Mongo.Exporting; using StellaOps.Plugin; @@ -74,8 +76,8 @@ public sealed class TrivyDbFeedExporter : IFeedExporter _logger.LogInformation("Starting Trivy DB export {ExportId}", exportId); var jsonBuilder = new JsonExportSnapshotBuilder(_options.Json, _pathResolver); - var advisoryStream = _advisoryStore.StreamAsync(cancellationToken); - var jsonResult = await jsonBuilder.WriteAsync(advisoryStream, exportedAt, exportId, cancellationToken).ConfigureAwait(false); + var advisories = await LoadAdvisoriesAsync(cancellationToken).ConfigureAwait(false); + var jsonResult = await jsonBuilder.WriteAsync(advisories, exportedAt, exportId, cancellationToken).ConfigureAwait(false); _logger.LogInformation( "Prepared Trivy JSON tree {ExportId} with {AdvisoryCount} advisories ({Bytes} bytes)", @@ -150,6 +152,23 @@ public sealed class TrivyDbFeedExporter : IFeedExporter } } + private async Task> LoadAdvisoriesAsync(CancellationToken cancellationToken) + { + var advisories = new List(); + await foreach (var advisory in _advisoryStore.StreamAsync(cancellationToken).ConfigureAwait(false)) + { + if (advisory is null) + { + continue; + } + + advisories.Add(advisory); + } + + advisories.Sort(static (left, right) => string.CompareOrdinal(left.AdvisoryKey, right.AdvisoryKey)); + return advisories; + } + private byte[] CreateMetadataJson( ReadOnlyMemory builderMetadata, string treeDigest, diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbMediaTypes.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbMediaTypes.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbMediaTypes.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbMediaTypes.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriteResult.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriteResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriteResult.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriteResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs similarity index 90% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs index bf91c1b9..c599a328 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOciWriter.cs @@ -53,7 +53,7 @@ public sealed class TrivyDbOciWriter Directory.CreateDirectory(root); var timestamp = package.Config.GeneratedAt.UtcDateTime; - await WriteFileAsync(Path.Combine(root, "metadata.json"), package.MetadataJson.ToArray(), timestamp, cancellationToken).ConfigureAwait(false); + await WriteFileAsync(Path.Combine(root, "metadata.json"), package.MetadataJson, timestamp, cancellationToken).ConfigureAwait(false); await WriteFileAsync(Path.Combine(root, "oci-layout"), OciLayoutBytes, timestamp, cancellationToken).ConfigureAwait(false); var blobsRoot = Path.Combine(root, "blobs", "sha256"); @@ -96,7 +96,7 @@ public sealed class TrivyDbOciWriter return new TrivyDbOciWriteResult(root, manifestDigest, blobDigests); } - private static async Task WriteFileAsync(string path, byte[] bytes, DateTime utcTimestamp, CancellationToken cancellationToken) + private static async Task WriteFileAsync(string path, ReadOnlyMemory bytes, DateTime utcTimestamp, CancellationToken cancellationToken) { var directory = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directory)) @@ -105,7 +105,15 @@ public sealed class TrivyDbOciWriter Directory.SetLastWriteTimeUtc(directory, utcTimestamp); } - await File.WriteAllBytesAsync(path, bytes, cancellationToken).ConfigureAwait(false); + await using var destination = new FileStream( + path, + FileMode.Create, + FileAccess.Write, + FileShare.None, + bufferSize: 81920, + options: FileOptions.Asynchronous | FileOptions.SequentialScan); + await destination.WriteAsync(bytes, cancellationToken).ConfigureAwait(false); + await destination.FlushAsync(cancellationToken).ConfigureAwait(false); File.SetLastWriteTimeUtc(path, utcTimestamp); } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOrasPusher.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOrasPusher.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOrasPusher.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbOrasPusher.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackage.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackage.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackage.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackage.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs similarity index 97% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs index 5fc1a96d..2f5c8a45 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs +++ b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageBuilder.cs @@ -85,7 +85,7 @@ public sealed class TrivyDbPackageBuilder configDescriptor, ImmutableArray.Create(layerDescriptor)); - var blobs = new Dictionary(StringComparer.Ordinal) + var blobs = new SortedDictionary(StringComparer.Ordinal) { [configDigest] = TrivyDbBlob.FromBytes(configBytes), [request.DatabaseDigest] = TrivyDbBlob.FromFile(request.DatabaseArchivePath, request.DatabaseLength), diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageRequest.cs b/src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageRequest.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageRequest.cs rename to src/StellaOps.Feedser.Exporter.TrivyDb/TrivyDbPackageRequest.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs b/src/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs similarity index 75% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs rename to src/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs index 470eab38..126676c9 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs +++ b/src/StellaOps.Feedser.Merge.Tests/AdvisoryPrecedenceMergerTests.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Time.Testing; +using StellaOps.Feedser.Merge.Options; using StellaOps.Feedser.Merge.Services; using StellaOps.Feedser.Models; @@ -14,6 +17,155 @@ public sealed class AdvisoryPrecedenceMergerTests var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero)); var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); + var (redHat, nvd) = CreateVendorAndRegistryAdvisories(); + var expectedMergeTimestamp = timeProvider.GetUtcNow(); + + var merged = merger.Merge(new[] { nvd, redHat }); + + Assert.Equal("CVE-2025-1000", merged.AdvisoryKey); + Assert.Equal("Red Hat Security Advisory", merged.Title); + Assert.Equal("Vendor-confirmed impact on RHEL 9.", merged.Summary); + Assert.Equal("high", merged.Severity); + Assert.Equal(redHat.Published, merged.Published); + Assert.Equal(redHat.Modified, merged.Modified); + Assert.Contains("RHSA-2025:0001", merged.Aliases); + Assert.Contains("CVE-2025-1000", merged.Aliases); + + var package = Assert.Single(merged.AffectedPackages); + Assert.Equal("cpe:2.3:o:redhat:enterprise_linux:9:*:*:*:*:*:*:*", package.Identifier); + Assert.Empty(package.VersionRanges); // NVD range suppressed by vendor precedence + Assert.Contains(package.Statuses, status => status.Status == "known_affected"); + Assert.Contains(package.Provenance, provenance => provenance.Source == "redhat"); + Assert.Contains(package.Provenance, provenance => provenance.Source == "nvd"); + + Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "redhat"); + Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "nvd"); + + var mergeProvenance = merged.Provenance.Single(p => p.Source == "merge"); + Assert.Equal("precedence", mergeProvenance.Kind); + Assert.Equal(expectedMergeTimestamp, mergeProvenance.RecordedAt); + Assert.Contains("redhat", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); + Assert.Contains("nvd", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void Merge_KevOnlyTogglesExploitKnown() + { + var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 2, 1, 0, 0, 0, TimeSpan.Zero)); + var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); + + var nvdProvenance = new AdvisoryProvenance("nvd", "document", "https://nvd", timeProvider.GetUtcNow()); + var baseAdvisory = new Advisory( + "CVE-2025-2000", + "CVE-2025-2000", + "Base registry summary", + "en", + new DateTimeOffset(2025, 1, 5, 0, 0, 0, TimeSpan.Zero), + new DateTimeOffset(2025, 1, 6, 0, 0, 0, TimeSpan.Zero), + "medium", + exploitKnown: false, + aliases: new[] { "CVE-2025-2000" }, + references: Array.Empty(), + affectedPackages: new[] + { + new AffectedPackage( + AffectedPackageTypes.Cpe, + "cpe:2.3:a:example:product:2.0:*:*:*:*:*:*:*", + null, + new[] + { + new AffectedVersionRange( + "semver", + "2.0.0", + "2.0.5", + null, + "<2.0.5", + new AdvisoryProvenance("nvd", "cpe_match", "product", timeProvider.GetUtcNow())) + }, + Array.Empty(), + new[] { nvdProvenance }) + }, + cvssMetrics: Array.Empty(), + provenance: new[] { nvdProvenance }); + + var kevProvenance = new AdvisoryProvenance("kev", "catalog", "CVE-2025-2000", timeProvider.GetUtcNow()); + var kevAdvisory = new Advisory( + "CVE-2025-2000", + "Known Exploited Vulnerability", + summary: null, + language: null, + published: null, + modified: null, + severity: null, + exploitKnown: true, + aliases: new[] { "KEV-CVE-2025-2000" }, + references: Array.Empty(), + affectedPackages: Array.Empty(), + cvssMetrics: Array.Empty(), + provenance: new[] { kevProvenance }); + + var merged = merger.Merge(new[] { baseAdvisory, kevAdvisory }); + + Assert.True(merged.ExploitKnown); + Assert.Equal("medium", merged.Severity); // KEV must not override severity + Assert.Equal("Base registry summary", merged.Summary); + Assert.Contains("CVE-2025-2000", merged.Aliases); + Assert.Contains("KEV-CVE-2025-2000", merged.Aliases); + Assert.Contains(merged.Provenance, provenance => provenance.Source == "kev"); + Assert.Contains(merged.Provenance, provenance => provenance.Source == "merge"); + } + + [Fact] + public void Merge_RespectsConfiguredPrecedenceOverrides() + { + var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 3, 1, 0, 0, 0, TimeSpan.Zero)); + var options = new AdvisoryPrecedenceOptions + { + Ranks = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["nvd"] = 0, + ["redhat"] = 5, + } + }; + + var logger = new TestLogger(); + using var metrics = new MetricCollector("StellaOps.Feedser.Merge"); + + var merger = new AdvisoryPrecedenceMerger( + new AffectedPackagePrecedenceResolver(), + options, + timeProvider, + logger); + + var (redHat, nvd) = CreateVendorAndRegistryAdvisories(); + var merged = merger.Merge(new[] { redHat, nvd }); + + Assert.Equal("CVE-2025-1000", merged.AdvisoryKey); + Assert.Equal("CVE-2025-1000", merged.Title); // NVD preferred + Assert.Equal("NVD summary", merged.Summary); + Assert.Equal("medium", merged.Severity); + + var package = Assert.Single(merged.AffectedPackages); + Assert.NotEmpty(package.VersionRanges); // Vendor range no longer overrides + Assert.Contains(package.Provenance, provenance => provenance.Source == "nvd"); + Assert.Contains(package.Provenance, provenance => provenance.Source == "redhat"); + + var overrideMeasurement = Assert.Single(metrics.Measurements, m => m.Name == "feedser.merge.overrides"); + Assert.Equal(1, overrideMeasurement.Value); + Assert.Contains(overrideMeasurement.Tags, tag => tag.Key == "primary_source" && string.Equals(tag.Value?.ToString(), "nvd", StringComparison.OrdinalIgnoreCase)); + Assert.Contains(overrideMeasurement.Tags, tag => tag.Key == "suppressed_source" && tag.Value?.ToString()?.Contains("redhat", StringComparison.OrdinalIgnoreCase) == true); + + var logEntry = Assert.Single(logger.Entries, entry => entry.EventId.Name == "AdvisoryOverride"); + Assert.Equal(LogLevel.Information, logEntry.Level); + Assert.NotNull(logEntry.StructuredState); + Assert.Contains(logEntry.StructuredState!, kvp => + (string.Equals(kvp.Key, "Override", StringComparison.Ordinal) || + string.Equals(kvp.Key, "@Override", StringComparison.Ordinal)) && + kvp.Value is not null); + } + + private static (Advisory Vendor, Advisory Registry) CreateVendorAndRegistryAdvisories() + { var redHatPublished = new DateTimeOffset(2025, 1, 10, 0, 0, 0, TimeSpan.Zero); var redHatModified = redHatPublished.AddDays(1); var redHatProvenance = new AdvisoryProvenance("redhat", "advisory", "RHSA-2025:0001", redHatModified); @@ -105,100 +257,6 @@ public sealed class AdvisoryPrecedenceMergerTests }, provenance: new[] { nvdProvenance }); - var expectedMergeTimestamp = timeProvider.GetUtcNow(); - - var merged = merger.Merge(new[] { nvd, redHat }); - - Assert.Equal("CVE-2025-1000", merged.AdvisoryKey); - Assert.Equal("Red Hat Security Advisory", merged.Title); - Assert.Equal("Vendor-confirmed impact on RHEL 9.", merged.Summary); - Assert.Equal("high", merged.Severity); - Assert.Equal(redHatPublished, merged.Published); - Assert.Equal(redHatModified, merged.Modified); - Assert.Contains("RHSA-2025:0001", merged.Aliases); - Assert.Contains("CVE-2025-1000", merged.Aliases); - - var package = Assert.Single(merged.AffectedPackages); - Assert.Equal("cpe:2.3:o:redhat:enterprise_linux:9:*:*:*:*:*:*:*", package.Identifier); - Assert.Empty(package.VersionRanges); // NVD range suppressed by vendor precedence - Assert.Contains(package.Statuses, status => status.Status == "known_affected"); - Assert.Contains(package.Provenance, provenance => provenance.Source == "redhat"); - Assert.Contains(package.Provenance, provenance => provenance.Source == "nvd"); - - Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "redhat"); - Assert.Contains(merged.CvssMetrics, metric => metric.Provenance.Source == "nvd"); - - var mergeProvenance = merged.Provenance.Single(p => p.Source == "merge"); - Assert.Equal("precedence", mergeProvenance.Kind); - Assert.Equal(expectedMergeTimestamp, mergeProvenance.RecordedAt); - Assert.Contains("redhat", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); - Assert.Contains("nvd", mergeProvenance.Value, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void Merge_KevOnlyTogglesExploitKnown() - { - var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 2, 1, 0, 0, 0, TimeSpan.Zero)); - var merger = new AdvisoryPrecedenceMerger(new AffectedPackagePrecedenceResolver(), timeProvider); - - var nvdProvenance = new AdvisoryProvenance("nvd", "document", "https://nvd", timeProvider.GetUtcNow()); - var baseAdvisory = new Advisory( - "CVE-2025-2000", - "CVE-2025-2000", - "Base registry summary", - "en", - new DateTimeOffset(2025, 1, 5, 0, 0, 0, TimeSpan.Zero), - new DateTimeOffset(2025, 1, 6, 0, 0, 0, TimeSpan.Zero), - "medium", - exploitKnown: false, - aliases: new[] { "CVE-2025-2000" }, - references: Array.Empty(), - affectedPackages: new[] - { - new AffectedPackage( - AffectedPackageTypes.Cpe, - "cpe:2.3:a:example:product:2.0:*:*:*:*:*:*:*", - null, - new[] - { - new AffectedVersionRange( - "semver", - "2.0.0", - "2.0.5", - null, - "<2.0.5", - new AdvisoryProvenance("nvd", "cpe_match", "product", timeProvider.GetUtcNow())) - }, - Array.Empty(), - new[] { nvdProvenance }) - }, - cvssMetrics: Array.Empty(), - provenance: new[] { nvdProvenance }); - - var kevProvenance = new AdvisoryProvenance("kev", "catalog", "CVE-2025-2000", timeProvider.GetUtcNow()); - var kevAdvisory = new Advisory( - "CVE-2025-2000", - "Known Exploited Vulnerability", - summary: null, - language: null, - published: null, - modified: null, - severity: null, - exploitKnown: true, - aliases: new[] { "KEV-CVE-2025-2000" }, - references: Array.Empty(), - affectedPackages: Array.Empty(), - cvssMetrics: Array.Empty(), - provenance: new[] { kevProvenance }); - - var merged = merger.Merge(new[] { baseAdvisory, kevAdvisory }); - - Assert.True(merged.ExploitKnown); - Assert.Equal("medium", merged.Severity); // KEV must not override severity - Assert.Equal("Base registry summary", merged.Summary); - Assert.Contains("CVE-2025-2000", merged.Aliases); - Assert.Contains("KEV-CVE-2025-2000", merged.Aliases); - Assert.Contains(merged.Provenance, provenance => provenance.Source == "kev"); - Assert.Contains(merged.Provenance, provenance => provenance.Source == "merge"); + return (redHat, nvd); } } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/AffectedPackagePrecedenceResolverTests.cs b/src/StellaOps.Feedser.Merge.Tests/AffectedPackagePrecedenceResolverTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/AffectedPackagePrecedenceResolverTests.cs rename to src/StellaOps.Feedser.Merge.Tests/AffectedPackagePrecedenceResolverTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/CanonicalHashCalculatorTests.cs b/src/StellaOps.Feedser.Merge.Tests/CanonicalHashCalculatorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/CanonicalHashCalculatorTests.cs rename to src/StellaOps.Feedser.Merge.Tests/CanonicalHashCalculatorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/DebianEvrComparerTests.cs b/src/StellaOps.Feedser.Merge.Tests/DebianEvrComparerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/DebianEvrComparerTests.cs rename to src/StellaOps.Feedser.Merge.Tests/DebianEvrComparerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/MergeEventWriterTests.cs b/src/StellaOps.Feedser.Merge.Tests/MergeEventWriterTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/MergeEventWriterTests.cs rename to src/StellaOps.Feedser.Merge.Tests/MergeEventWriterTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/MergePrecedenceIntegrationTests.cs b/src/StellaOps.Feedser.Merge.Tests/MergePrecedenceIntegrationTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/MergePrecedenceIntegrationTests.cs rename to src/StellaOps.Feedser.Merge.Tests/MergePrecedenceIntegrationTests.cs diff --git a/src/StellaOps.Feedser.Merge.Tests/MetricCollector.cs b/src/StellaOps.Feedser.Merge.Tests/MetricCollector.cs new file mode 100644 index 00000000..2531672a --- /dev/null +++ b/src/StellaOps.Feedser.Merge.Tests/MetricCollector.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; + +namespace StellaOps.Feedser.Merge.Tests; + +internal sealed class MetricCollector : IDisposable +{ + private readonly MeterListener _listener; + private readonly List _measurements = new(); + + public MetricCollector(string meterName) + { + if (string.IsNullOrWhiteSpace(meterName)) + { + throw new ArgumentException("Meter name is required", nameof(meterName)); + } + + _listener = new MeterListener + { + InstrumentPublished = (instrument, listener) => + { + if (instrument.Meter.Name == meterName) + { + listener.EnableMeasurementEvents(instrument); + } + } + }; + + _listener.SetMeasurementEventCallback((instrument, measurement, tags, state) => + { + var tagArray = new KeyValuePair[tags.Length]; + for (var i = 0; i < tags.Length; i++) + { + tagArray[i] = tags[i]; + } + + _measurements.Add(new MetricMeasurement(instrument.Name, measurement, tagArray)); + }); + + _listener.Start(); + } + + public IReadOnlyList Measurements => _measurements; + + public void Dispose() + { + _listener.Dispose(); + } + + internal sealed record MetricMeasurement( + string Name, + long Value, + IReadOnlyList> Tags); +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/NevraComparerTests.cs b/src/StellaOps.Feedser.Merge.Tests/NevraComparerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/NevraComparerTests.cs rename to src/StellaOps.Feedser.Merge.Tests/NevraComparerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/SemanticVersionRangeResolverTests.cs b/src/StellaOps.Feedser.Merge.Tests/SemanticVersionRangeResolverTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/SemanticVersionRangeResolverTests.cs rename to src/StellaOps.Feedser.Merge.Tests/SemanticVersionRangeResolverTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/StellaOps.Feedser.Merge.Tests.csproj b/src/StellaOps.Feedser.Merge.Tests/StellaOps.Feedser.Merge.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge.Tests/StellaOps.Feedser.Merge.Tests.csproj rename to src/StellaOps.Feedser.Merge.Tests/StellaOps.Feedser.Merge.Tests.csproj diff --git a/src/StellaOps.Feedser.Merge.Tests/TestLogger.cs b/src/StellaOps.Feedser.Merge.Tests/TestLogger.cs new file mode 100644 index 00000000..aa250a3a --- /dev/null +++ b/src/StellaOps.Feedser.Merge.Tests/TestLogger.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace StellaOps.Feedser.Merge.Tests; + +internal sealed class TestLogger : ILogger +{ + private static readonly IDisposable NoopScope = new DisposableScope(); + + public List Entries { get; } = new(); + + public IDisposable BeginScope(TState state) + where TState : notnull + => NoopScope; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (formatter is null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + IReadOnlyList>? structuredState = null; + if (state is IReadOnlyList> list) + { + structuredState = list.ToArray(); + } + else if (state is IEnumerable> enumerable) + { + structuredState = enumerable.ToArray(); + } + + Entries.Add(new LogEntry(logLevel, eventId, formatter(state, exception), structuredState)); + } + + internal sealed record LogEntry( + LogLevel Level, + EventId EventId, + string Message, + IReadOnlyList>? StructuredState); + + private sealed class DisposableScope : IDisposable + { + public void Dispose() + { + } + } +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/AGENTS.md b/src/StellaOps.Feedser.Merge/AGENTS.md similarity index 87% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/AGENTS.md rename to src/StellaOps.Feedser.Merge/AGENTS.md index 976c6662..6c45a964 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/AGENTS.md +++ b/src/StellaOps.Feedser.Merge/AGENTS.md @@ -17,6 +17,10 @@ Deterministic merge and reconciliation engine; builds identity graph via aliases - Precedence table configurable but with sane defaults: RedHat/Ubuntu/Debian/SUSE > Vendor PSIRT > GHSA/OSV > NVD; CERTs enrich; KEV sets flags. - Range selection uses comparers: NevraComparer, DebEvrComparer, SemVerRange; deterministic tie-breakers. - Provenance propagation merges unique entries; references deduped by (url, type). + +## Configuration +- Precedence overrides bind via `feedser:merge:precedence:ranks` (dictionary of `source` → `rank`, lower wins). Absent entries fall back to defaults. +- Operator workflow: update `etc/feedser.yaml` or environment variables, restart merge job; overrides surface in metrics/logs as `AdvisoryOverride` entries. ## In/Out of scope In: merge logic, precedence policy, hashing, event records, comparers. Out: fetching/parsing, exporter packaging, signing. @@ -27,4 +31,3 @@ Out: fetching/parsing, exporter packaging, signing. - Author and review coverage in `../StellaOps.Feedser.Merge.Tests`. - Shared fixtures (e.g., `MongoIntegrationFixture`, `ConnectorTestHarness`) live in `../StellaOps.Feedser.Testing`. - Keep fixtures deterministic; match new cases to real-world advisories or regression scenarios. - diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Class1.cs b/src/StellaOps.Feedser.Merge/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Class1.cs rename to src/StellaOps.Feedser.Merge/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/DebianEvr.cs b/src/StellaOps.Feedser.Merge/Comparers/DebianEvr.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/DebianEvr.cs rename to src/StellaOps.Feedser.Merge/Comparers/DebianEvr.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/Nevra.cs b/src/StellaOps.Feedser.Merge/Comparers/Nevra.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/Nevra.cs rename to src/StellaOps.Feedser.Merge/Comparers/Nevra.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/SemanticVersionRangeResolver.cs b/src/StellaOps.Feedser.Merge/Comparers/SemanticVersionRangeResolver.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Comparers/SemanticVersionRangeResolver.cs rename to src/StellaOps.Feedser.Merge/Comparers/SemanticVersionRangeResolver.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceOptions.cs b/src/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceOptions.cs rename to src/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceOptions.cs diff --git a/src/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceTable.cs b/src/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceTable.cs new file mode 100644 index 00000000..a5495af1 --- /dev/null +++ b/src/StellaOps.Feedser.Merge/Options/AdvisoryPrecedenceTable.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace StellaOps.Feedser.Merge.Options; + +internal static class AdvisoryPrecedenceTable +{ + public static IReadOnlyDictionary Merge( + IReadOnlyDictionary defaults, + AdvisoryPrecedenceOptions? options) + { + if (defaults is null) + { + throw new ArgumentNullException(nameof(defaults)); + } + + if (options?.Ranks is null || options.Ranks.Count == 0) + { + return defaults; + } + + var merged = new Dictionary(defaults, StringComparer.OrdinalIgnoreCase); + foreach (var kvp in options.Ranks) + { + if (string.IsNullOrWhiteSpace(kvp.Key)) + { + continue; + } + + merged[kvp.Key.Trim()] = kvp.Value; + } + + return merged; + } +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs b/src/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs similarity index 73% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs rename to src/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs index 320cb470..348a4825 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs +++ b/src/StellaOps.Feedser.Merge/Services/AdvisoryPrecedenceMerger.cs @@ -24,6 +24,7 @@ public sealed class AdvisoryPrecedenceMerger ["oracle"] = 1, ["adobe"] = 1, ["chromium"] = 1, + ["vendor"] = 1, ["jvn"] = 2, ["certfr"] = 2, ["certin"] = 2, @@ -38,6 +39,11 @@ public sealed class AdvisoryPrecedenceMerger unit: "count", description: "Number of times lower-precedence advisories were overridden by higher-precedence sources."); + private static readonly Action OverrideLogged = LoggerMessage.Define( + LogLevel.Information, + new EventId(1000, "AdvisoryOverride"), + "Advisory precedence override {@Override}"); + private readonly AffectedPackagePrecedenceResolver _packageResolver; private readonly IReadOnlyDictionary _precedence; private readonly int _fallbackRank; @@ -67,7 +73,11 @@ public sealed class AdvisoryPrecedenceMerger AdvisoryPrecedenceOptions? options, System.TimeProvider timeProvider, ILogger? logger = null) - : this(packageResolver, MergePrecedence(DefaultPrecedence, options), timeProvider, logger) + : this( + EnsureResolver(packageResolver, options, out var precedence), + precedence, + timeProvider, + logger) { } @@ -222,29 +232,6 @@ public sealed class AdvisoryPrecedenceMerger return best; } - private static IReadOnlyDictionary MergePrecedence( - IReadOnlyDictionary defaults, - AdvisoryPrecedenceOptions? options) - { - if (options?.Ranks is null || options.Ranks.Count == 0) - { - return defaults; - } - - var merged = new Dictionary(defaults, StringComparer.OrdinalIgnoreCase); - foreach (var kvp in options.Ranks) - { - if (string.IsNullOrWhiteSpace(kvp.Key)) - { - continue; - } - - merged[kvp.Key.Trim()] = kvp.Value; - } - - return merged; - } - private void LogOverrides(string advisoryKey, IReadOnlyList ordered) { if (ordered.Count <= 1) @@ -254,7 +241,6 @@ public sealed class AdvisoryPrecedenceMerger var primary = ordered[0]; var primaryRank = primary.Rank; - var primarySources = string.Join(',', primary.Sources); for (var i = 1; i < ordered.Count; i++) { @@ -264,24 +250,28 @@ public sealed class AdvisoryPrecedenceMerger continue; } - var suppressedSources = string.Join(',', candidate.Sources); + var tags = new KeyValuePair[] + { + new("primary_source", FormatSourceLabel(primary.Sources)), + new("suppressed_source", FormatSourceLabel(candidate.Sources)), + new("primary_rank", primaryRank), + new("suppressed_rank", candidate.Rank), + }; - OverridesCounter.Add( - 1, - new KeyValuePair[] - { - new("advisory", advisoryKey), - new("primary_sources", primarySources), - new("suppressed_sources", suppressedSources), - }); + OverridesCounter.Add(1, tags); - _logger.LogInformation( - "Advisory precedence override for {AdvisoryKey}: kept {PrimarySources} (rank {PrimaryRank}) over {SuppressedSources} (rank {SuppressedRank})", + var audit = new MergeOverrideAudit( advisoryKey, - primarySources, + primary.Sources, primaryRank, - suppressedSources, - candidate.Rank); + candidate.Sources, + candidate.Rank, + primary.Advisory.Aliases.Length, + candidate.Advisory.Aliases.Length, + primary.Advisory.Provenance.Length, + candidate.Advisory.Provenance.Length); + + OverrideLogged(_logger, audit, null); } } @@ -293,4 +283,75 @@ public sealed class AdvisoryPrecedenceMerger .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); } + + private static AffectedPackagePrecedenceResolver EnsureResolver( + AffectedPackagePrecedenceResolver? resolver, + AdvisoryPrecedenceOptions? options, + out IReadOnlyDictionary precedence) + { + precedence = AdvisoryPrecedenceTable.Merge(DefaultPrecedence, options); + + if (resolver is null) + { + return new AffectedPackagePrecedenceResolver(precedence); + } + + if (DictionaryEquals(resolver.Precedence, precedence)) + { + return resolver; + } + + return new AffectedPackagePrecedenceResolver(precedence); + } + + private static bool DictionaryEquals( + IReadOnlyDictionary left, + IReadOnlyDictionary right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left.Count != right.Count) + { + return false; + } + + foreach (var (key, value) in left) + { + if (!right.TryGetValue(key, out var other) || other != value) + { + return false; + } + } + + return true; + } + + private static string FormatSourceLabel(IReadOnlyCollection sources) + { + if (sources.Count == 0) + { + return "unknown"; + } + + if (sources.Count == 1) + { + return sources.First(); + } + + return string.Join('|', sources.OrderBy(static s => s, StringComparer.OrdinalIgnoreCase).Take(3)); + } + + private readonly record struct MergeOverrideAudit( + string AdvisoryKey, + IReadOnlyCollection PrimarySources, + int PrimaryRank, + IReadOnlyCollection SuppressedSources, + int SuppressedRank, + int PrimaryAliasCount, + int SuppressedAliasCount, + int PrimaryProvenanceCount, + int SuppressedProvenanceCount); } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs b/src/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs similarity index 91% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs rename to src/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs index f6c32047..59028d8f 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs +++ b/src/StellaOps.Feedser.Merge/Services/AffectedPackagePrecedenceResolver.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using StellaOps.Feedser.Merge.Options; using StellaOps.Feedser.Models; namespace StellaOps.Feedser.Merge.Services; @@ -21,6 +22,7 @@ public sealed class AffectedPackagePrecedenceResolver ["oracle"] = 1, ["adobe"] = 1, ["chromium"] = 1, + ["vendor"] = 1, ["nvd"] = 5, }; @@ -32,12 +34,19 @@ public sealed class AffectedPackagePrecedenceResolver { } + public AffectedPackagePrecedenceResolver(AdvisoryPrecedenceOptions? options) + : this(AdvisoryPrecedenceTable.Merge(DefaultPrecedence, options)) + { + } + public AffectedPackagePrecedenceResolver(IReadOnlyDictionary precedence) { _precedence = precedence ?? throw new ArgumentNullException(nameof(precedence)); _fallbackRank = precedence.Count == 0 ? 10 : precedence.Values.Max() + 1; } + public IReadOnlyDictionary Precedence => _precedence; + public IReadOnlyList Merge(IEnumerable packages) { ArgumentNullException.ThrowIfNull(packages); diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/CanonicalHashCalculator.cs b/src/StellaOps.Feedser.Merge/Services/CanonicalHashCalculator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/CanonicalHashCalculator.cs rename to src/StellaOps.Feedser.Merge/Services/CanonicalHashCalculator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/MergeEventWriter.cs b/src/StellaOps.Feedser.Merge/Services/MergeEventWriter.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/Services/MergeEventWriter.cs rename to src/StellaOps.Feedser.Merge/Services/MergeEventWriter.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/StellaOps.Feedser.Merge.csproj b/src/StellaOps.Feedser.Merge/StellaOps.Feedser.Merge.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/StellaOps.Feedser.Merge.csproj rename to src/StellaOps.Feedser.Merge/StellaOps.Feedser.Merge.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/TASKS.md b/src/StellaOps.Feedser.Merge/TASKS.md similarity index 77% rename from src/StellaOps.Feedser/StellaOps.Feedser.Merge/TASKS.md rename to src/StellaOps.Feedser.Merge/TASKS.md index 92caae65..37122944 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Merge/TASKS.md +++ b/src/StellaOps.Feedser.Merge/TASKS.md @@ -9,5 +9,5 @@ |Canonical hash and merge_event writer|BE-Merge|Models, Storage.Mongo|DONE – Hash calculator + MergeEventWriter compute canonical SHA-256 digests and persist merge events.| |Conflict detection and metrics|BE-Merge|Core|Counters; structured logs; traces.| |End-to-end determinism test|QA|Merge, key connectors|Same inputs -> same hashes.| -|Override audit logging|BE-Merge|Observability|DOING – structured override logging and metrics emitted; await production telemetry review.| -|Configurable precedence table|BE-Merge|Architecture|DOING – precedence overrides now accepted via options; document operator workflow.| +|Override audit logging|BE-Merge|Observability|DONE – override audits now emit structured logs plus bounded-tag metrics suitable for prod telemetry.| +|Configurable precedence table|BE-Merge|Architecture|DONE – precedence options bind via feedser:merge:precedence:ranks with docs/tests covering operator workflow.| diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AdvisoryTests.cs b/src/StellaOps.Feedser.Models.Tests/AdvisoryTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AdvisoryTests.cs rename to src/StellaOps.Feedser.Models.Tests/AdvisoryTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AffectedPackageStatusTests.cs b/src/StellaOps.Feedser.Models.Tests/AffectedPackageStatusTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AffectedPackageStatusTests.cs rename to src/StellaOps.Feedser.Models.Tests/AffectedPackageStatusTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AliasSchemeRegistryTests.cs b/src/StellaOps.Feedser.Models.Tests/AliasSchemeRegistryTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/AliasSchemeRegistryTests.cs rename to src/StellaOps.Feedser.Models.Tests/AliasSchemeRegistryTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalExampleFactory.cs b/src/StellaOps.Feedser.Models.Tests/CanonicalExampleFactory.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalExampleFactory.cs rename to src/StellaOps.Feedser.Models.Tests/CanonicalExampleFactory.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalExamplesTests.cs b/src/StellaOps.Feedser.Models.Tests/CanonicalExamplesTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalExamplesTests.cs rename to src/StellaOps.Feedser.Models.Tests/CanonicalExamplesTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalJsonSerializerTests.cs b/src/StellaOps.Feedser.Models.Tests/CanonicalJsonSerializerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/CanonicalJsonSerializerTests.cs rename to src/StellaOps.Feedser.Models.Tests/CanonicalJsonSerializerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/SeverityNormalizationTests.cs b/src/StellaOps.Feedser.Models.Tests/SeverityNormalizationTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/SeverityNormalizationTests.cs rename to src/StellaOps.Feedser.Models.Tests/SeverityNormalizationTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/StellaOps.Feedser.Models.Tests.csproj b/src/StellaOps.Feedser.Models.Tests/StellaOps.Feedser.Models.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models.Tests/StellaOps.Feedser.Models.Tests.csproj rename to src/StellaOps.Feedser.Models.Tests/StellaOps.Feedser.Models.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AGENTS.md b/src/StellaOps.Feedser.Models/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AGENTS.md rename to src/StellaOps.Feedser.Models/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/Advisory.cs b/src/StellaOps.Feedser.Models/Advisory.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/Advisory.cs rename to src/StellaOps.Feedser.Models/Advisory.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AdvisoryProvenance.cs b/src/StellaOps.Feedser.Models/AdvisoryProvenance.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AdvisoryProvenance.cs rename to src/StellaOps.Feedser.Models/AdvisoryProvenance.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AdvisoryReference.cs b/src/StellaOps.Feedser.Models/AdvisoryReference.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AdvisoryReference.cs rename to src/StellaOps.Feedser.Models/AdvisoryReference.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackage.cs b/src/StellaOps.Feedser.Models/AffectedPackage.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackage.cs rename to src/StellaOps.Feedser.Models/AffectedPackage.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackageStatus.cs b/src/StellaOps.Feedser.Models/AffectedPackageStatus.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackageStatus.cs rename to src/StellaOps.Feedser.Models/AffectedPackageStatus.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackageStatusCatalog.cs b/src/StellaOps.Feedser.Models/AffectedPackageStatusCatalog.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedPackageStatusCatalog.cs rename to src/StellaOps.Feedser.Models/AffectedPackageStatusCatalog.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedVersionRange.cs b/src/StellaOps.Feedser.Models/AffectedVersionRange.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AffectedVersionRange.cs rename to src/StellaOps.Feedser.Models/AffectedVersionRange.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AliasSchemeRegistry.cs b/src/StellaOps.Feedser.Models/AliasSchemeRegistry.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AliasSchemeRegistry.cs rename to src/StellaOps.Feedser.Models/AliasSchemeRegistry.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/AliasSchemes.cs b/src/StellaOps.Feedser.Models/AliasSchemes.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/AliasSchemes.cs rename to src/StellaOps.Feedser.Models/AliasSchemes.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/BACKWARD_COMPATIBILITY.md b/src/StellaOps.Feedser.Models/BACKWARD_COMPATIBILITY.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/BACKWARD_COMPATIBILITY.md rename to src/StellaOps.Feedser.Models/BACKWARD_COMPATIBILITY.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/CANONICAL_RECORDS.md b/src/StellaOps.Feedser.Models/CANONICAL_RECORDS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/CANONICAL_RECORDS.md rename to src/StellaOps.Feedser.Models/CANONICAL_RECORDS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/CanonicalJsonSerializer.cs b/src/StellaOps.Feedser.Models/CanonicalJsonSerializer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/CanonicalJsonSerializer.cs rename to src/StellaOps.Feedser.Models/CanonicalJsonSerializer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/CvssMetric.cs b/src/StellaOps.Feedser.Models/CvssMetric.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/CvssMetric.cs rename to src/StellaOps.Feedser.Models/CvssMetric.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/PROVENANCE_GUIDELINES.md b/src/StellaOps.Feedser.Models/PROVENANCE_GUIDELINES.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/PROVENANCE_GUIDELINES.md rename to src/StellaOps.Feedser.Models/PROVENANCE_GUIDELINES.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/SeverityNormalization.cs b/src/StellaOps.Feedser.Models/SeverityNormalization.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/SeverityNormalization.cs rename to src/StellaOps.Feedser.Models/SeverityNormalization.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/SnapshotSerializer.cs b/src/StellaOps.Feedser.Models/SnapshotSerializer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/SnapshotSerializer.cs rename to src/StellaOps.Feedser.Models/SnapshotSerializer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/StellaOps.Feedser.Models.csproj b/src/StellaOps.Feedser.Models/StellaOps.Feedser.Models.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/StellaOps.Feedser.Models.csproj rename to src/StellaOps.Feedser.Models/StellaOps.Feedser.Models.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/TASKS.md b/src/StellaOps.Feedser.Models/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/TASKS.md rename to src/StellaOps.Feedser.Models/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Models/Validation.cs b/src/StellaOps.Feedser.Models/Validation.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Models/Validation.cs rename to src/StellaOps.Feedser.Models/Validation.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/CpeNormalizerTests.cs b/src/StellaOps.Feedser.Normalization.Tests/CpeNormalizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/CpeNormalizerTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/CpeNormalizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/CvssMetricNormalizerTests.cs b/src/StellaOps.Feedser.Normalization.Tests/CvssMetricNormalizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/CvssMetricNormalizerTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/CvssMetricNormalizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/DebianEvrParserTests.cs b/src/StellaOps.Feedser.Normalization.Tests/DebianEvrParserTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/DebianEvrParserTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/DebianEvrParserTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/DescriptionNormalizerTests.cs b/src/StellaOps.Feedser.Normalization.Tests/DescriptionNormalizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/DescriptionNormalizerTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/DescriptionNormalizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/NevraParserTests.cs b/src/StellaOps.Feedser.Normalization.Tests/NevraParserTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/NevraParserTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/NevraParserTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/PackageUrlNormalizerTests.cs b/src/StellaOps.Feedser.Normalization.Tests/PackageUrlNormalizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/PackageUrlNormalizerTests.cs rename to src/StellaOps.Feedser.Normalization.Tests/PackageUrlNormalizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/StellaOps.Feedser.Normalization.Tests.csproj b/src/StellaOps.Feedser.Normalization.Tests/StellaOps.Feedser.Normalization.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization.Tests/StellaOps.Feedser.Normalization.Tests.csproj rename to src/StellaOps.Feedser.Normalization.Tests/StellaOps.Feedser.Normalization.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/AssemblyInfo.cs b/src/StellaOps.Feedser.Normalization/AssemblyInfo.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/AssemblyInfo.cs rename to src/StellaOps.Feedser.Normalization/AssemblyInfo.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Cvss/CvssMetricNormalizer.cs b/src/StellaOps.Feedser.Normalization/Cvss/CvssMetricNormalizer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Cvss/CvssMetricNormalizer.cs rename to src/StellaOps.Feedser.Normalization/Cvss/CvssMetricNormalizer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Distro/DebianEvr.cs b/src/StellaOps.Feedser.Normalization/Distro/DebianEvr.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Distro/DebianEvr.cs rename to src/StellaOps.Feedser.Normalization/Distro/DebianEvr.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Distro/Nevra.cs b/src/StellaOps.Feedser.Normalization/Distro/Nevra.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Distro/Nevra.cs rename to src/StellaOps.Feedser.Normalization/Distro/Nevra.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/Cpe23.cs b/src/StellaOps.Feedser.Normalization/Identifiers/Cpe23.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/Cpe23.cs rename to src/StellaOps.Feedser.Normalization/Identifiers/Cpe23.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/IdentifierNormalizer.cs b/src/StellaOps.Feedser.Normalization/Identifiers/IdentifierNormalizer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/IdentifierNormalizer.cs rename to src/StellaOps.Feedser.Normalization/Identifiers/IdentifierNormalizer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/PackageUrl.cs b/src/StellaOps.Feedser.Normalization/Identifiers/PackageUrl.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Identifiers/PackageUrl.cs rename to src/StellaOps.Feedser.Normalization/Identifiers/PackageUrl.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/StellaOps.Feedser.Normalization.csproj b/src/StellaOps.Feedser.Normalization/StellaOps.Feedser.Normalization.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/StellaOps.Feedser.Normalization.csproj rename to src/StellaOps.Feedser.Normalization/StellaOps.Feedser.Normalization.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/TASKS.md b/src/StellaOps.Feedser.Normalization/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/TASKS.md rename to src/StellaOps.Feedser.Normalization/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Text/DescriptionNormalizer.cs b/src/StellaOps.Feedser.Normalization/Text/DescriptionNormalizer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Normalization/Text/DescriptionNormalizer.cs rename to src/StellaOps.Feedser.Normalization/Text/DescriptionNormalizer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Acsc/Class1.cs b/src/StellaOps.Feedser.Source.Acsc/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Acsc/Class1.cs rename to src/StellaOps.Feedser.Source.Acsc/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj b/src/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj rename to src/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj +++ b/src/StellaOps.Feedser.Source.Acsc/StellaOps.Feedser.Source.Acsc.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cccs/Class1.cs b/src/StellaOps.Feedser.Source.Cccs/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Cccs/Class1.cs rename to src/StellaOps.Feedser.Source.Cccs/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj b/src/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj rename to src/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj +++ b/src/StellaOps.Feedser.Source.Cccs/StellaOps.Feedser.Source.Cccs.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertBund/Class1.cs b/src/StellaOps.Feedser.Source.CertBund/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertBund/Class1.cs rename to src/StellaOps.Feedser.Source.CertBund/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj b/src/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj rename to src/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj +++ b/src/StellaOps.Feedser.Source.CertBund/StellaOps.Feedser.Source.CertBund.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertCc/Class1.cs b/src/StellaOps.Feedser.Source.CertCc/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertCc/Class1.cs rename to src/StellaOps.Feedser.Source.CertCc/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj b/src/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj rename to src/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj +++ b/src/StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/CertFrConnectorTests.cs b/src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/CertFrConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/CertFrConnectorTests.cs rename to src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/CertFrConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-advisories.snapshot.json b/src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-advisories.snapshot.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-advisories.snapshot.json rename to src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-advisories.snapshot.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-001.html b/src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-001.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-001.html rename to src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-001.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-002.html b/src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-002.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-002.html rename to src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-detail-AV-2024-002.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-feed.xml b/src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-feed.xml similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-feed.xml rename to src/StellaOps.Feedser.Source.CertFr.Tests/CertFr/Fixtures/certfr-feed.xml diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/StellaOps.Feedser.Source.CertFr.Tests.csproj b/src/StellaOps.Feedser.Source.CertFr.Tests/StellaOps.Feedser.Source.CertFr.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr.Tests/StellaOps.Feedser.Source.CertFr.Tests.csproj rename to src/StellaOps.Feedser.Source.CertFr.Tests/StellaOps.Feedser.Source.CertFr.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/AGENTS.md b/src/StellaOps.Feedser.Source.CertFr/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/AGENTS.md rename to src/StellaOps.Feedser.Source.CertFr/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrConnector.cs b/src/StellaOps.Feedser.Source.CertFr/CertFrConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrConnector.cs rename to src/StellaOps.Feedser.Source.CertFr/CertFrConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrConnectorPlugin.cs b/src/StellaOps.Feedser.Source.CertFr/CertFrConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.CertFr/CertFrConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.CertFr/CertFrDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.CertFr/CertFrDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.CertFr/CertFrServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/CertFrServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.CertFr/CertFrServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Configuration/CertFrOptions.cs b/src/StellaOps.Feedser.Source.CertFr/Configuration/CertFrOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Configuration/CertFrOptions.cs rename to src/StellaOps.Feedser.Source.CertFr/Configuration/CertFrOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrCursor.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrCursor.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrDocumentMetadata.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrDocumentMetadata.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrDocumentMetadata.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrDocumentMetadata.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrDto.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrDto.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedClient.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedClient.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedClient.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedClient.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedItem.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedItem.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrFeedItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrMapper.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrMapper.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrParser.cs b/src/StellaOps.Feedser.Source.CertFr/Internal/CertFrParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Internal/CertFrParser.cs rename to src/StellaOps.Feedser.Source.CertFr/Internal/CertFrParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Jobs.cs b/src/StellaOps.Feedser.Source.CertFr/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/Jobs.cs rename to src/StellaOps.Feedser.Source.CertFr/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj b/src/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj similarity index 85% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj rename to src/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj index 3a14a80c..9e3f378e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj +++ b/src/StellaOps.Feedser.Source.CertFr/StellaOps.Feedser.Source.CertFr.csproj @@ -5,7 +5,7 @@ enable - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/TASKS.md b/src/StellaOps.Feedser.Source.CertFr/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertFr/TASKS.md rename to src/StellaOps.Feedser.Source.CertFr/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/CertInConnectorTests.cs b/src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/CertInConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/CertInConnectorTests.cs rename to src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/CertInConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/alerts-page1.json b/src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/alerts-page1.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/alerts-page1.json rename to src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/alerts-page1.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/detail-CIAD-2024-0005.html b/src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/detail-CIAD-2024-0005.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/detail-CIAD-2024-0005.html rename to src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/detail-CIAD-2024-0005.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/expected-advisory.json b/src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/expected-advisory.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/expected-advisory.json rename to src/StellaOps.Feedser.Source.CertIn.Tests/CertIn/Fixtures/expected-advisory.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/StellaOps.Feedser.Source.CertIn.Tests.csproj b/src/StellaOps.Feedser.Source.CertIn.Tests/StellaOps.Feedser.Source.CertIn.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn.Tests/StellaOps.Feedser.Source.CertIn.Tests.csproj rename to src/StellaOps.Feedser.Source.CertIn.Tests/StellaOps.Feedser.Source.CertIn.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/AGENTS.md b/src/StellaOps.Feedser.Source.CertIn/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/AGENTS.md rename to src/StellaOps.Feedser.Source.CertIn/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInConnector.cs b/src/StellaOps.Feedser.Source.CertIn/CertInConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInConnector.cs rename to src/StellaOps.Feedser.Source.CertIn/CertInConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInConnectorPlugin.cs b/src/StellaOps.Feedser.Source.CertIn/CertInConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.CertIn/CertInConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.CertIn/CertInDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.CertIn/CertInDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.CertIn/CertInServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/CertInServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.CertIn/CertInServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Configuration/CertInOptions.cs b/src/StellaOps.Feedser.Source.CertIn/Configuration/CertInOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Configuration/CertInOptions.cs rename to src/StellaOps.Feedser.Source.CertIn/Configuration/CertInOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInAdvisoryDto.cs b/src/StellaOps.Feedser.Source.CertIn/Internal/CertInAdvisoryDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInAdvisoryDto.cs rename to src/StellaOps.Feedser.Source.CertIn/Internal/CertInAdvisoryDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInClient.cs b/src/StellaOps.Feedser.Source.CertIn/Internal/CertInClient.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInClient.cs rename to src/StellaOps.Feedser.Source.CertIn/Internal/CertInClient.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInCursor.cs b/src/StellaOps.Feedser.Source.CertIn/Internal/CertInCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInCursor.cs rename to src/StellaOps.Feedser.Source.CertIn/Internal/CertInCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInDetailParser.cs b/src/StellaOps.Feedser.Source.CertIn/Internal/CertInDetailParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInDetailParser.cs rename to src/StellaOps.Feedser.Source.CertIn/Internal/CertInDetailParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInListingItem.cs b/src/StellaOps.Feedser.Source.CertIn/Internal/CertInListingItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Internal/CertInListingItem.cs rename to src/StellaOps.Feedser.Source.CertIn/Internal/CertInListingItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Jobs.cs b/src/StellaOps.Feedser.Source.CertIn/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/Jobs.cs rename to src/StellaOps.Feedser.Source.CertIn/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj b/src/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj similarity index 85% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj rename to src/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj index 18eae8a0..07f798f6 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj +++ b/src/StellaOps.Feedser.Source.CertIn/StellaOps.Feedser.Source.CertIn.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/TASKS.md b/src/StellaOps.Feedser.Source.CertIn/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.CertIn/TASKS.md rename to src/StellaOps.Feedser.Source.CertIn/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/CannedHttpMessageHandlerTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/CannedHttpMessageHandlerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/CannedHttpMessageHandlerTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/CannedHttpMessageHandlerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/HtmlContentSanitizerTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/HtmlContentSanitizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/HtmlContentSanitizerTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/HtmlContentSanitizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/PackageCoordinateHelperTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/PackageCoordinateHelperTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/PackageCoordinateHelperTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/PackageCoordinateHelperTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/PdfTextExtractorTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/PdfTextExtractorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/PdfTextExtractorTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/PdfTextExtractorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/SourceFetchServiceTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/SourceFetchServiceTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/SourceFetchServiceTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/SourceFetchServiceTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/TimeWindowCursorPlannerTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/TimeWindowCursorPlannerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/TimeWindowCursorPlannerTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/TimeWindowCursorPlannerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/UrlNormalizerTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Common/UrlNormalizerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Common/UrlNormalizerTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Common/UrlNormalizerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Json/JsonSchemaValidatorTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Json/JsonSchemaValidatorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Json/JsonSchemaValidatorTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Json/JsonSchemaValidatorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/StellaOps.Feedser.Source.Common.Tests.csproj b/src/StellaOps.Feedser.Source.Common.Tests/StellaOps.Feedser.Source.Common.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/StellaOps.Feedser.Source.Common.Tests.csproj rename to src/StellaOps.Feedser.Source.Common.Tests/StellaOps.Feedser.Source.Common.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Xml/XmlSchemaValidatorTests.cs b/src/StellaOps.Feedser.Source.Common.Tests/Xml/XmlSchemaValidatorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common.Tests/Xml/XmlSchemaValidatorTests.cs rename to src/StellaOps.Feedser.Source.Common.Tests/Xml/XmlSchemaValidatorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/AGENTS.md b/src/StellaOps.Feedser.Source.Common/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/AGENTS.md rename to src/StellaOps.Feedser.Source.Common/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/PaginationPlanner.cs b/src/StellaOps.Feedser.Source.Common/Cursors/PaginationPlanner.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/PaginationPlanner.cs rename to src/StellaOps.Feedser.Source.Common/Cursors/PaginationPlanner.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorOptions.cs b/src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorOptions.cs rename to src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorPlanner.cs b/src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorPlanner.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorPlanner.cs rename to src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorPlanner.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorState.cs b/src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorState.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorState.cs rename to src/StellaOps.Feedser.Source.Common/Cursors/TimeWindowCursorState.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/DocumentStatuses.cs b/src/StellaOps.Feedser.Source.Common/DocumentStatuses.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/DocumentStatuses.cs rename to src/StellaOps.Feedser.Source.Common/DocumentStatuses.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/CryptoJitterSource.cs b/src/StellaOps.Feedser.Source.Common/Fetch/CryptoJitterSource.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/CryptoJitterSource.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/CryptoJitterSource.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/IJitterSource.cs b/src/StellaOps.Feedser.Source.Common/Fetch/IJitterSource.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/IJitterSource.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/IJitterSource.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/RawDocumentStorage.cs b/src/StellaOps.Feedser.Source.Common/Fetch/RawDocumentStorage.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/RawDocumentStorage.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/RawDocumentStorage.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchContentResult.cs b/src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchContentResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchContentResult.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchContentResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchRequest.cs b/src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchRequest.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchRequest.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchRequest.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchResult.cs b/src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchResult.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchResult.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchResult.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchService.cs b/src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchService.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceFetchService.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/SourceFetchService.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceRetryPolicy.cs b/src/StellaOps.Feedser.Source.Common/Fetch/SourceRetryPolicy.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Fetch/SourceRetryPolicy.cs rename to src/StellaOps.Feedser.Source.Common/Fetch/SourceRetryPolicy.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Html/HtmlContentSanitizer.cs b/src/StellaOps.Feedser.Source.Common/Html/HtmlContentSanitizer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Html/HtmlContentSanitizer.cs rename to src/StellaOps.Feedser.Source.Common/Html/HtmlContentSanitizer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/AllowlistedHttpMessageHandler.cs b/src/StellaOps.Feedser.Source.Common/Http/AllowlistedHttpMessageHandler.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/AllowlistedHttpMessageHandler.cs rename to src/StellaOps.Feedser.Source.Common/Http/AllowlistedHttpMessageHandler.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/ServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Common/Http/ServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/ServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Common/Http/ServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/SourceHttpClientOptions.cs b/src/StellaOps.Feedser.Source.Common/Http/SourceHttpClientOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Http/SourceHttpClientOptions.cs rename to src/StellaOps.Feedser.Source.Common/Http/SourceHttpClientOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/IJsonSchemaValidator.cs b/src/StellaOps.Feedser.Source.Common/Json/IJsonSchemaValidator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/IJsonSchemaValidator.cs rename to src/StellaOps.Feedser.Source.Common/Json/IJsonSchemaValidator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationError.cs b/src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationError.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationError.cs rename to src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationError.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationException.cs b/src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationException.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationException.cs rename to src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidationException.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidator.cs b/src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidator.cs rename to src/StellaOps.Feedser.Source.Common/Json/JsonSchemaValidator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Packages/PackageCoordinateHelper.cs b/src/StellaOps.Feedser.Source.Common/Packages/PackageCoordinateHelper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Packages/PackageCoordinateHelper.cs rename to src/StellaOps.Feedser.Source.Common/Packages/PackageCoordinateHelper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Pdf/PdfTextExtractor.cs b/src/StellaOps.Feedser.Source.Common/Pdf/PdfTextExtractor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Pdf/PdfTextExtractor.cs rename to src/StellaOps.Feedser.Source.Common/Pdf/PdfTextExtractor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Properties/AssemblyInfo.cs b/src/StellaOps.Feedser.Source.Common/Properties/AssemblyInfo.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Properties/AssemblyInfo.cs rename to src/StellaOps.Feedser.Source.Common/Properties/AssemblyInfo.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj b/src/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj similarity index 91% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj rename to src/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj index 8841e740..272e2054 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj +++ b/src/StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj @@ -16,6 +16,6 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/TASKS.md b/src/StellaOps.Feedser.Source.Common/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/TASKS.md rename to src/StellaOps.Feedser.Source.Common/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Telemetry/SourceDiagnostics.cs b/src/StellaOps.Feedser.Source.Common/Telemetry/SourceDiagnostics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Telemetry/SourceDiagnostics.cs rename to src/StellaOps.Feedser.Source.Common/Telemetry/SourceDiagnostics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Testing/CannedHttpMessageHandler.cs b/src/StellaOps.Feedser.Source.Common/Testing/CannedHttpMessageHandler.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Testing/CannedHttpMessageHandler.cs rename to src/StellaOps.Feedser.Source.Common/Testing/CannedHttpMessageHandler.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Url/UrlNormalizer.cs b/src/StellaOps.Feedser.Source.Common/Url/UrlNormalizer.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Url/UrlNormalizer.cs rename to src/StellaOps.Feedser.Source.Common/Url/UrlNormalizer.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/IXmlSchemaValidator.cs b/src/StellaOps.Feedser.Source.Common/Xml/IXmlSchemaValidator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/IXmlSchemaValidator.cs rename to src/StellaOps.Feedser.Source.Common/Xml/IXmlSchemaValidator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationError.cs b/src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationError.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationError.cs rename to src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationError.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationException.cs b/src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationException.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationException.cs rename to src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidationException.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidator.cs b/src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidator.cs rename to src/StellaOps.Feedser.Source.Common/Xml/XmlSchemaValidator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cve/Class1.cs b/src/StellaOps.Feedser.Source.Cve/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Cve/Class1.cs rename to src/StellaOps.Feedser.Source.Cve/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj b/src/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj rename to src/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj +++ b/src/StellaOps.Feedser.Source.Cve/StellaOps.Feedser.Source.Cve.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Debian/Class1.cs b/src/StellaOps.Feedser.Source.Distro.Debian/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Debian/Class1.cs rename to src/StellaOps.Feedser.Source.Distro.Debian/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj b/src/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj rename to src/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj +++ b/src/StellaOps.Feedser.Source.Distro.Debian/StellaOps.Feedser.Source.Distro.Debian.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0001.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0001.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0001.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0001.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0002.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0002.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0002.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0002.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0003.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0003.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0003.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/csaf-rhsa-2025-0003.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/rhsa-2025-0001.snapshot.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/rhsa-2025-0001.snapshot.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/rhsa-2025-0001.snapshot.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/rhsa-2025-0001.snapshot.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1-repeat.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1-repeat.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1-repeat.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1-repeat.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page1.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page2.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page2.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page2.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page2.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page3.json b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page3.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page3.json rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/Fixtures/summary-page3.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorHarnessTests.cs b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorHarnessTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorHarnessTests.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorHarnessTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorTests.cs b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorTests.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/RedHat/RedHatConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj b/src/StellaOps.Feedser.Source.Distro.RedHat.Tests/StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat.Tests/StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj rename to src/StellaOps.Feedser.Source.Distro.RedHat.Tests/StellaOps.Feedser.Source.Distro.RedHat.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/AGENTS.md b/src/StellaOps.Feedser.Source.Distro.RedHat/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/AGENTS.md rename to src/StellaOps.Feedser.Source.Distro.RedHat/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Configuration/RedHatOptions.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Configuration/RedHatOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Configuration/RedHatOptions.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Configuration/RedHatOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/Models/RedHatCsafModels.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Internal/Models/RedHatCsafModels.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/Models/RedHatCsafModels.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Internal/Models/RedHatCsafModels.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatCursor.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatCursor.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatMapper.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatMapper.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatSummaryItem.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatSummaryItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatSummaryItem.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Internal/RedHatSummaryItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Jobs.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/Jobs.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnector.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnector.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/RedHatConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/RedHatDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/RedHatDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Distro.RedHat/RedHatServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/RedHatServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Distro.RedHat/RedHatServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj b/src/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj similarity index 75% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj rename to src/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj index 2ed24a26..7af3a126 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj +++ b/src/StellaOps.Feedser.Source.Distro.RedHat/StellaOps.Feedser.Source.Distro.RedHat.csproj @@ -5,8 +5,8 @@ enable - - + + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md b/src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md rename to src/StellaOps.Feedser.Source.Distro.RedHat/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Suse/Class1.cs b/src/StellaOps.Feedser.Source.Distro.Suse/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Suse/Class1.cs rename to src/StellaOps.Feedser.Source.Distro.Suse/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj b/src/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj rename to src/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj +++ b/src/StellaOps.Feedser.Source.Distro.Suse/StellaOps.Feedser.Source.Distro.Suse.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Ubuntu/Class1.cs b/src/StellaOps.Feedser.Source.Distro.Ubuntu/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Ubuntu/Class1.cs rename to src/StellaOps.Feedser.Source.Distro.Ubuntu/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj b/src/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj rename to src/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj +++ b/src/StellaOps.Feedser.Source.Distro.Ubuntu/StellaOps.Feedser.Source.Distro.Ubuntu.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ghsa/Class1.cs b/src/StellaOps.Feedser.Source.Ghsa/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ghsa/Class1.cs rename to src/StellaOps.Feedser.Source.Ghsa/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj b/src/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj rename to src/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj +++ b/src/StellaOps.Feedser.Source.Ghsa/StellaOps.Feedser.Source.Ghsa.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Cisa/Class1.cs b/src/StellaOps.Feedser.Source.Ics.Cisa/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Cisa/Class1.cs rename to src/StellaOps.Feedser.Source.Ics.Cisa/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj b/src/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj rename to src/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj +++ b/src/StellaOps.Feedser.Source.Ics.Cisa/StellaOps.Feedser.Source.Ics.Cisa.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/detail-acme-controller-2024.html b/src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/detail-acme-controller-2024.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/detail-acme-controller-2024.html rename to src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/detail-acme-controller-2024.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/expected-advisory.json b/src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/expected-advisory.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/expected-advisory.json rename to src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/expected-advisory.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/feed-page1.xml b/src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/feed-page1.xml similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/feed-page1.xml rename to src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/Fixtures/feed-page1.xml diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/KasperskyConnectorTests.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/KasperskyConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/KasperskyConnectorTests.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/Kaspersky/KasperskyConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj b/src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj rename to src/StellaOps.Feedser.Source.Ics.Kaspersky.Tests/StellaOps.Feedser.Source.Ics.Kaspersky.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/AGENTS.md b/src/StellaOps.Feedser.Source.Ics.Kaspersky/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/AGENTS.md rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Configuration/KasperskyOptions.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Configuration/KasperskyOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Configuration/KasperskyOptions.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Configuration/KasperskyOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryDto.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryDto.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryParser.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryParser.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyAdvisoryParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyCursor.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyCursor.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedClient.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedClient.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedClient.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedClient.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedItem.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedItem.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Internal/KasperskyFeedItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Jobs.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/Jobs.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnector.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnector.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/KasperskyServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj b/src/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj similarity index 85% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj index 18eae8a0..07f798f6 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj +++ b/src/StellaOps.Feedser.Source.Ics.Kaspersky/StellaOps.Feedser.Source.Ics.Kaspersky.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/TASKS.md b/src/StellaOps.Feedser.Source.Ics.Kaspersky/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ics.Kaspersky/TASKS.md rename to src/StellaOps.Feedser.Source.Ics.Kaspersky/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/expected-advisory.json b/src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/expected-advisory.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/expected-advisory.json rename to src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/expected-advisory.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/jvnrss-window1.xml b/src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/jvnrss-window1.xml similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/jvnrss-window1.xml rename to src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/jvnrss-window1.xml diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/vuldef-JVNDB-2024-123456.xml b/src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/vuldef-JVNDB-2024-123456.xml similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/vuldef-JVNDB-2024-123456.xml rename to src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/Fixtures/vuldef-JVNDB-2024-123456.xml diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/JvnConnectorTests.cs b/src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/JvnConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/Jvn/JvnConnectorTests.cs rename to src/StellaOps.Feedser.Source.Jvn.Tests/Jvn/JvnConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/StellaOps.Feedser.Source.Jvn.Tests.csproj b/src/StellaOps.Feedser.Source.Jvn.Tests/StellaOps.Feedser.Source.Jvn.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn.Tests/StellaOps.Feedser.Source.Jvn.Tests.csproj rename to src/StellaOps.Feedser.Source.Jvn.Tests/StellaOps.Feedser.Source.Jvn.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/AGENTS.md b/src/StellaOps.Feedser.Source.Jvn/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/AGENTS.md rename to src/StellaOps.Feedser.Source.Jvn/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Configuration/JvnOptions.cs b/src/StellaOps.Feedser.Source.Jvn/Configuration/JvnOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Configuration/JvnOptions.cs rename to src/StellaOps.Feedser.Source.Jvn/Configuration/JvnOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnAdvisoryMapper.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnAdvisoryMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnAdvisoryMapper.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnAdvisoryMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnConstants.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnConstants.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnConstants.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnConstants.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnCursor.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnCursor.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailDto.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailDto.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailParser.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailParser.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnDetailParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewItem.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewItem.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewPage.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewPage.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewPage.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnOverviewPage.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaProvider.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaProvider.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaProvider.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaProvider.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaValidationException.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaValidationException.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaValidationException.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/JvnSchemaValidationException.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/MyJvnClient.cs b/src/StellaOps.Feedser.Source.Jvn/Internal/MyJvnClient.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Internal/MyJvnClient.cs rename to src/StellaOps.Feedser.Source.Jvn/Internal/MyJvnClient.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Jobs.cs b/src/StellaOps.Feedser.Source.Jvn/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Jobs.cs rename to src/StellaOps.Feedser.Source.Jvn/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnConnector.cs b/src/StellaOps.Feedser.Source.Jvn/JvnConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnConnector.cs rename to src/StellaOps.Feedser.Source.Jvn/JvnConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Jvn/JvnConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Jvn/JvnConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.Jvn/JvnDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.Jvn/JvnDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Jvn/JvnServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/JvnServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Jvn/JvnServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/data_marking.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/data_marking.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/data_marking.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/data_marking.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/jvnrss_3.2.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/jvnrss_3.2.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/jvnrss_3.2.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/jvnrss_3.2.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/mod_sec_3.0.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/mod_sec_3.0.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/mod_sec_3.0.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/mod_sec_3.0.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/status_3.3.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/status_3.3.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/status_3.3.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/status_3.3.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/tlp_marking.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/tlp_marking.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/tlp_marking.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/tlp_marking.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/vuldef_3.2.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/vuldef_3.2.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/vuldef_3.2.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/vuldef_3.2.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/xml.xsd b/src/StellaOps.Feedser.Source.Jvn/Schemas/xml.xsd similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/Schemas/xml.xsd rename to src/StellaOps.Feedser.Source.Jvn/Schemas/xml.xsd diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj b/src/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj similarity index 88% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj rename to src/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj index 2ad3888a..96ffa805 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj +++ b/src/StellaOps.Feedser.Source.Jvn/StellaOps.Feedser.Source.Jvn.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/TASKS.md b/src/StellaOps.Feedser.Source.Jvn/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Jvn/TASKS.md rename to src/StellaOps.Feedser.Source.Jvn/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kev/Class1.cs b/src/StellaOps.Feedser.Source.Kev/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Kev/Class1.cs rename to src/StellaOps.Feedser.Source.Kev/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj b/src/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj rename to src/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj +++ b/src/StellaOps.Feedser.Source.Kev/StellaOps.Feedser.Source.Kev.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kisa/Class1.cs b/src/StellaOps.Feedser.Source.Kisa/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Kisa/Class1.cs rename to src/StellaOps.Feedser.Source.Kisa/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj b/src/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj rename to src/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj +++ b/src/StellaOps.Feedser.Source.Kisa/StellaOps.Feedser.Source.Kisa.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-invalid-schema.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-invalid-schema.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-invalid-schema.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-invalid-schema.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-1.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-1.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-1.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-1.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-2.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-2.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-2.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-2.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-3.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-3.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-3.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-multipage-3.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-1.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-1.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-1.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-1.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-2.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-2.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-2.json rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-2.json diff --git a/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-update.json b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-update.json new file mode 100644 index 00000000..f7be7b3a --- /dev/null +++ b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/Fixtures/nvd-window-update.json @@ -0,0 +1,51 @@ +{ + "resultsPerPage": 2000, + "startIndex": 0, + "totalResults": 1, + "vulnerabilities": [ + { + "cve": { + "id": "CVE-2024-0001", + "sourceIdentifier": "nvd@nist.gov", + "published": "2024-01-01T10:00:00Z", + "lastModified": "2024-01-03T12:00:00Z", + "descriptions": [ + { "lang": "en", "value": "Example vulnerability one updated." } + ], + "references": [ + { + "url": "https://vendor.example.com/advisories/0001", + "source": "Vendor", + "tags": ["Vendor Advisory"] + }, + { + "url": "https://kb.example.com/articles/0001", + "source": "KnowledgeBase", + "tags": ["Third Party Advisory"] + } + ], + "metrics": { + "cvssMetricV31": [ + { + "cvssData": { + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "baseScore": 8.8, + "baseSeverity": "HIGH" + } + } + ] + }, + "configurations": { + "nodes": [ + { + "cpeMatch": [ + { "vulnerable": true, "criteria": "cpe:2.3:a:example:product_one:1.0:*:*:*:*:*:*:*" }, + { "vulnerable": true, "criteria": "cpe:2.3:a:example:product_one:1.1:*:*:*:*:*:*:*" } + ] + } + ] + } + } + } + ] +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorHarnessTests.cs b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorHarnessTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorHarnessTests.cs rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorHarnessTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs similarity index 84% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs rename to src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs index c6fc0df6..73d8e707 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs +++ b/src/StellaOps.Feedser.Source.Nvd.Tests/Nvd/NvdConnectorTests.cs @@ -23,6 +23,7 @@ using StellaOps.Feedser.Storage.Mongo; using StellaOps.Feedser.Storage.Mongo.Advisories; using StellaOps.Feedser.Storage.Mongo.Documents; using StellaOps.Feedser.Storage.Mongo.Dtos; +using StellaOps.Feedser.Storage.Mongo.ChangeHistory; using StellaOps.Feedser.Testing; namespace StellaOps.Feedser.Source.Nvd.Tests; @@ -197,6 +198,75 @@ public sealed class NvdConnectorTests : IAsyncLifetime Assert.Equal(5, collector.GetValue("nvd.map.success")); } + [Fact] + public async Task ChangeHistory_RecordsDifferencesForModifiedCve() + { + await ResetDatabaseAsync(); + + var options = new NvdOptions + { + BaseEndpoint = new Uri("https://nvd.example.test/api"), + WindowSize = TimeSpan.FromHours(1), + WindowOverlap = TimeSpan.FromMinutes(5), + InitialBackfill = TimeSpan.FromHours(2), + }; + + var windowStart = _timeProvider.GetUtcNow() - options.InitialBackfill; + var windowEnd = windowStart + options.WindowSize; + _handler.AddJsonResponse(BuildRequestUri(options, windowStart, windowEnd), ReadFixture("nvd-window-1.json")); + + await EnsureServiceProviderAsync(options); + var provider = _serviceProvider!; + var connector = new NvdConnectorPlugin().Create(provider); + + await connector.FetchAsync(provider, CancellationToken.None); + await connector.ParseAsync(provider, CancellationToken.None); + await connector.MapAsync(provider, CancellationToken.None); + + var historyStore = provider.GetRequiredService(); + var historyEntries = await historyStore.GetRecentAsync("nvd", "CVE-2024-0001", 5, CancellationToken.None); + Assert.Empty(historyEntries); + + _timeProvider.Advance(TimeSpan.FromHours(2)); + var now = _timeProvider.GetUtcNow(); + + var stateRepository = provider.GetRequiredService(); + var state = await stateRepository.TryGetAsync(NvdConnectorPlugin.SourceName, CancellationToken.None); + Assert.NotNull(state); + + var cursorDocument = state!.Cursor; + var lastWindowEnd = cursorDocument.TryGetValue("windowEnd", out var endValue) ? ReadDateTime(endValue) : (DateTimeOffset?)null; + var startCandidate = (lastWindowEnd ?? windowEnd) - options.WindowOverlap; + var backfillLimit = now - options.InitialBackfill; + var window2Start = startCandidate < backfillLimit ? backfillLimit : startCandidate; + var window2End = window2Start + options.WindowSize; + if (window2End > now) + { + window2End = now; + } + + _handler.AddJsonResponse(BuildRequestUri(options, window2Start, window2End), ReadFixture("nvd-window-update.json")); + + await connector.FetchAsync(provider, CancellationToken.None); + await connector.ParseAsync(provider, CancellationToken.None); + await connector.MapAsync(provider, CancellationToken.None); + + var advisoryStore = provider.GetRequiredService(); + var updatedAdvisory = await advisoryStore.FindAsync("CVE-2024-0001", CancellationToken.None); + Assert.NotNull(updatedAdvisory); + Assert.Equal("high", updatedAdvisory!.Severity); + + historyEntries = await historyStore.GetRecentAsync("nvd", "CVE-2024-0001", 5, CancellationToken.None); + Assert.NotEmpty(historyEntries); + var latest = historyEntries[0]; + Assert.Equal("nvd", latest.SourceName); + Assert.Equal("CVE-2024-0001", latest.AdvisoryKey); + Assert.NotNull(latest.PreviousHash); + Assert.NotEqual(latest.PreviousHash, latest.CurrentHash); + Assert.Contains(latest.Changes, change => change.Field == "severity" && change.ChangeType == "Modified"); + Assert.Contains(latest.Changes, change => change.Field == "references" && change.ChangeType == "Modified"); + } + [Fact] public async Task ParseAsync_InvalidSchema_QuarantinesDocumentAndEmitsMetric() { @@ -512,8 +582,20 @@ public sealed class NvdConnectorTests : IAsyncLifetime private static string ReadFixture(string filename) { - var path = Path.Combine(AppContext.BaseDirectory, "Source", "Nvd", "Fixtures", filename); - return File.ReadAllText(path); + var baseDirectory = AppContext.BaseDirectory; + var primary = Path.Combine(baseDirectory, "Source", "Nvd", "Fixtures", filename); + if (File.Exists(primary)) + { + return File.ReadAllText(primary); + } + + var secondary = Path.Combine(baseDirectory, "Nvd", "Fixtures", filename); + if (File.Exists(secondary)) + { + return File.ReadAllText(secondary); + } + + throw new FileNotFoundException($"Fixture '{filename}' was not found in the test output directory."); } public Task InitializeAsync() => Task.CompletedTask; diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/StellaOps.Feedser.Source.Nvd.Tests.csproj b/src/StellaOps.Feedser.Source.Nvd.Tests/StellaOps.Feedser.Source.Nvd.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd.Tests/StellaOps.Feedser.Source.Nvd.Tests.csproj rename to src/StellaOps.Feedser.Source.Nvd.Tests/StellaOps.Feedser.Source.Nvd.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/AGENTS.md b/src/StellaOps.Feedser.Source.Nvd/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/AGENTS.md rename to src/StellaOps.Feedser.Source.Nvd/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Configuration/NvdOptions.cs b/src/StellaOps.Feedser.Source.Nvd/Configuration/NvdOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Configuration/NvdOptions.cs rename to src/StellaOps.Feedser.Source.Nvd/Configuration/NvdOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdCursor.cs b/src/StellaOps.Feedser.Source.Nvd/Internal/NvdCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdCursor.cs rename to src/StellaOps.Feedser.Source.Nvd/Internal/NvdCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdDiagnostics.cs b/src/StellaOps.Feedser.Source.Nvd/Internal/NvdDiagnostics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdDiagnostics.cs rename to src/StellaOps.Feedser.Source.Nvd/Internal/NvdDiagnostics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdMapper.cs b/src/StellaOps.Feedser.Source.Nvd/Internal/NvdMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdMapper.cs rename to src/StellaOps.Feedser.Source.Nvd/Internal/NvdMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdSchemaProvider.cs b/src/StellaOps.Feedser.Source.Nvd/Internal/NvdSchemaProvider.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Internal/NvdSchemaProvider.cs rename to src/StellaOps.Feedser.Source.Nvd/Internal/NvdSchemaProvider.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdConnector.cs b/src/StellaOps.Feedser.Source.Nvd/NvdConnector.cs similarity index 80% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdConnector.cs rename to src/StellaOps.Feedser.Source.Nvd/NvdConnector.cs index d06eca57..51d8031e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdConnector.cs +++ b/src/StellaOps.Feedser.Source.Nvd/NvdConnector.cs @@ -1,4 +1,6 @@ using System.Globalization; +using System.Security.Cryptography; +using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -14,6 +16,7 @@ using StellaOps.Feedser.Storage.Mongo; using StellaOps.Feedser.Storage.Mongo.Advisories; using StellaOps.Feedser.Storage.Mongo.Documents; using StellaOps.Feedser.Storage.Mongo.Dtos; +using StellaOps.Feedser.Storage.Mongo.ChangeHistory; using StellaOps.Plugin; using Json.Schema; @@ -26,6 +29,7 @@ public sealed class NvdConnector : IFeedConnector private readonly IDocumentStore _documentStore; private readonly IDtoStore _dtoStore; private readonly IAdvisoryStore _advisoryStore; + private readonly IChangeHistoryStore _changeHistoryStore; private readonly ISourceStateRepository _stateRepository; private readonly IJsonSchemaValidator _schemaValidator; private readonly NvdOptions _options; @@ -41,6 +45,7 @@ public sealed class NvdConnector : IFeedConnector IDocumentStore documentStore, IDtoStore dtoStore, IAdvisoryStore advisoryStore, + IChangeHistoryStore changeHistoryStore, ISourceStateRepository stateRepository, IJsonSchemaValidator schemaValidator, IOptions options, @@ -53,6 +58,7 @@ public sealed class NvdConnector : IFeedConnector _documentStore = documentStore ?? throw new ArgumentNullException(nameof(documentStore)); _dtoStore = dtoStore ?? throw new ArgumentNullException(nameof(dtoStore)); _advisoryStore = advisoryStore ?? throw new ArgumentNullException(nameof(advisoryStore)); + _changeHistoryStore = changeHistoryStore ?? throw new ArgumentNullException(nameof(changeHistoryStore)); _stateRepository = stateRepository ?? throw new ArgumentNullException(nameof(stateRepository)); _schemaValidator = schemaValidator ?? throw new ArgumentNullException(nameof(schemaValidator)); _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); @@ -277,7 +283,12 @@ public sealed class NvdConnector : IFeedConnector continue; } + var previous = await _advisoryStore.FindAsync(advisory.AdvisoryKey, cancellationToken).ConfigureAwait(false); await _advisoryStore.UpsertAsync(advisory, cancellationToken).ConfigureAwait(false); + if (previous is not null) + { + await RecordChangeHistoryAsync(advisory, previous, document, now, cancellationToken).ConfigureAwait(false); + } mappedCount++; } @@ -418,6 +429,108 @@ public sealed class NvdConnector : IFeedConnector return false; } + private async Task RecordChangeHistoryAsync( + Advisory current, + Advisory previous, + DocumentRecord document, + DateTimeOffset capturedAt, + CancellationToken cancellationToken) + { + if (current.Equals(previous)) + { + return; + } + + var currentSnapshot = SnapshotSerializer.ToSnapshot(current); + var previousSnapshot = SnapshotSerializer.ToSnapshot(previous); + + if (string.Equals(currentSnapshot, previousSnapshot, StringComparison.Ordinal)) + { + return; + } + + var changes = ComputeChanges(previousSnapshot, currentSnapshot); + if (changes.Count == 0) + { + return; + } + + var documentHash = string.IsNullOrWhiteSpace(document.Sha256) + ? ComputeHash(currentSnapshot) + : document.Sha256; + + var record = new ChangeHistoryRecord( + Guid.NewGuid(), + SourceName, + current.AdvisoryKey, + document.Id, + documentHash, + ComputeHash(currentSnapshot), + ComputeHash(previousSnapshot), + currentSnapshot, + previousSnapshot, + changes, + capturedAt); + + await _changeHistoryStore.AddAsync(record, cancellationToken).ConfigureAwait(false); + } + + private static IReadOnlyList ComputeChanges(string previousSnapshot, string currentSnapshot) + { + using var previousDocument = JsonDocument.Parse(previousSnapshot); + using var currentDocument = JsonDocument.Parse(currentSnapshot); + + var previousRoot = previousDocument.RootElement; + var currentRoot = currentDocument.RootElement; + var fields = new HashSet(StringComparer.Ordinal); + + foreach (var property in previousRoot.EnumerateObject()) + { + fields.Add(property.Name); + } + + foreach (var property in currentRoot.EnumerateObject()) + { + fields.Add(property.Name); + } + + var changes = new List(); + foreach (var field in fields.OrderBy(static name => name, StringComparer.Ordinal)) + { + var hasPrevious = previousRoot.TryGetProperty(field, out var previousValue); + var hasCurrent = currentRoot.TryGetProperty(field, out var currentValue); + + if (!hasPrevious && hasCurrent) + { + changes.Add(new ChangeHistoryFieldChange(field, "Added", null, SerializeElement(currentValue))); + continue; + } + + if (hasPrevious && !hasCurrent) + { + changes.Add(new ChangeHistoryFieldChange(field, "Removed", SerializeElement(previousValue), null)); + continue; + } + + if (hasPrevious && hasCurrent && !JsonElement.DeepEquals(previousValue, currentValue)) + { + changes.Add(new ChangeHistoryFieldChange(field, "Modified", SerializeElement(previousValue), SerializeElement(currentValue))); + } + } + + return changes; + } + + private static string SerializeElement(JsonElement element) + => JsonSerializer.Serialize(element, new JsonSerializerOptions { WriteIndented = false }); + + private static string ComputeHash(string snapshot) + { + var bytes = Encoding.UTF8.GetBytes(snapshot); + var hash = SHA256.HashData(bytes); + return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}"; + } + private async Task GetCursorAsync(CancellationToken cancellationToken) { var record = await _stateRepository.TryGetAsync(SourceName, cancellationToken).ConfigureAwait(false); diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Nvd/NvdConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Nvd/NvdConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Nvd/NvdServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/NvdServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Nvd/NvdServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Schemas/nvd-vulnerability.schema.json b/src/StellaOps.Feedser.Source.Nvd/Schemas/nvd-vulnerability.schema.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/Schemas/nvd-vulnerability.schema.json rename to src/StellaOps.Feedser.Source.Nvd/Schemas/nvd-vulnerability.schema.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj b/src/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj similarity index 88% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj rename to src/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj index 72efb0c6..3673d8a6 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj +++ b/src/StellaOps.Feedser.Source.Nvd/StellaOps.Feedser.Source.Nvd.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/TASKS.md b/src/StellaOps.Feedser.Source.Nvd/TASKS.md similarity index 87% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/TASKS.md rename to src/StellaOps.Feedser.Source.Nvd/TASKS.md index d0004119..d3a919e5 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Nvd/TASKS.md +++ b/src/StellaOps.Feedser.Source.Nvd/TASKS.md @@ -8,6 +8,6 @@ |Integration test fixture isolation|QA|Storage.Mongo|**DONE** – connector tests reset Mongo/time fixtures between runs to avoid cross-test bleed.| |Tests: golden pages + resume|QA|Tests|**DONE** – snapshot and resume coverage added across `NvdConnectorTests`.| |Observability|BE-Conn-Nvd|Core|**DONE** – `NvdDiagnostics` meter tracks attempts/documents/failures with collector tests.| -|Change history snapshotting|BE-Conn-Nvd|Storage.Mongo|TODO – record modified CVE diffs when payload hashes change for auditing.| +|Change history snapshotting|BE-Conn-Nvd|Storage.Mongo|DONE – connector now records per-CVE snapshots with top-level diff metadata whenever canonical advisories change.| |Pagination for windows over page limit|BE-Conn-Nvd|Source.Common|**DONE** – additional page fetcher honors `startIndex`; covered by multipage tests.| |Schema validation quarantine path|BE-Conn-Nvd|Storage.Mongo|**DONE** – schema failures mark documents failed and metrics assert quarantine.| diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv.Tests/Osv/OsvMapperTests.cs b/src/StellaOps.Feedser.Source.Osv.Tests/Osv/OsvMapperTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv.Tests/Osv/OsvMapperTests.cs rename to src/StellaOps.Feedser.Source.Osv.Tests/Osv/OsvMapperTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv.Tests/StellaOps.Feedser.Source.Osv.Tests.csproj b/src/StellaOps.Feedser.Source.Osv.Tests/StellaOps.Feedser.Source.Osv.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv.Tests/StellaOps.Feedser.Source.Osv.Tests.csproj rename to src/StellaOps.Feedser.Source.Osv.Tests/StellaOps.Feedser.Source.Osv.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/AGENTS.md b/src/StellaOps.Feedser.Source.Osv/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/AGENTS.md rename to src/StellaOps.Feedser.Source.Osv/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Configuration/OsvOptions.cs b/src/StellaOps.Feedser.Source.Osv/Configuration/OsvOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Configuration/OsvOptions.cs rename to src/StellaOps.Feedser.Source.Osv/Configuration/OsvOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvCursor.cs b/src/StellaOps.Feedser.Source.Osv/Internal/OsvCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvCursor.cs rename to src/StellaOps.Feedser.Source.Osv/Internal/OsvCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvMapper.cs b/src/StellaOps.Feedser.Source.Osv/Internal/OsvMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvMapper.cs rename to src/StellaOps.Feedser.Source.Osv/Internal/OsvMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvVulnerabilityDto.cs b/src/StellaOps.Feedser.Source.Osv/Internal/OsvVulnerabilityDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Internal/OsvVulnerabilityDto.cs rename to src/StellaOps.Feedser.Source.Osv/Internal/OsvVulnerabilityDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Jobs.cs b/src/StellaOps.Feedser.Source.Osv/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/Jobs.cs rename to src/StellaOps.Feedser.Source.Osv/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvConnector.cs b/src/StellaOps.Feedser.Source.Osv/OsvConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvConnector.cs rename to src/StellaOps.Feedser.Source.Osv/OsvConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Osv/OsvConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Osv/OsvConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.Osv/OsvDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.Osv/OsvDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Osv/OsvServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/OsvServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Osv/OsvServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj b/src/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj similarity index 90% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj rename to src/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj index df032faa..664b10ec 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj +++ b/src/StellaOps.Feedser.Source.Osv/StellaOps.Feedser.Source.Osv.csproj @@ -5,7 +5,7 @@ enable - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/TASKS.md b/src/StellaOps.Feedser.Source.Osv/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Osv/TASKS.md rename to src/StellaOps.Feedser.Source.Osv/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Bdu/Class1.cs b/src/StellaOps.Feedser.Source.Ru.Bdu/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Bdu/Class1.cs rename to src/StellaOps.Feedser.Source.Ru.Bdu/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj b/src/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj rename to src/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj +++ b/src/StellaOps.Feedser.Source.Ru.Bdu/StellaOps.Feedser.Source.Ru.Bdu.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Nkcki/Class1.cs b/src/StellaOps.Feedser.Source.Ru.Nkcki/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Nkcki/Class1.cs rename to src/StellaOps.Feedser.Source.Ru.Nkcki/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj b/src/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj rename to src/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj +++ b/src/StellaOps.Feedser.Source.Ru.Nkcki/StellaOps.Feedser.Source.Ru.Nkcki.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/AdobeConnectorFetchTests.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/AdobeConnectorFetchTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/AdobeConnectorFetchTests.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/AdobeConnectorFetchTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-advisories.snapshot.json b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-advisories.snapshot.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-advisories.snapshot.json rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-advisories.snapshot.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-85.html b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-85.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-85.html rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-85.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-87.html b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-87.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-87.html rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-detail-apsb25-87.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-index.html b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-index.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-index.html rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/Adobe/Fixtures/adobe-index.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj b/src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe.Tests/StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj rename to src/StellaOps.Feedser.Source.Vndr.Adobe.Tests/StellaOps.Feedser.Source.Vndr.Adobe.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AGENTS.md b/src/StellaOps.Feedser.Source.Vndr.Adobe/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AGENTS.md rename to src/StellaOps.Feedser.Source.Vndr.Adobe/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnector.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnector.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeDiagnostics.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeDiagnostics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeDiagnostics.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeDiagnostics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/AdobeServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/AdobeServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Configuration/AdobeOptions.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Configuration/AdobeOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Configuration/AdobeOptions.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Configuration/AdobeOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeBulletinDto.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeBulletinDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeBulletinDto.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeBulletinDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeCursor.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeCursor.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDetailParser.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDetailParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDetailParser.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDetailParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDocumentMetadata.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDocumentMetadata.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDocumentMetadata.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeDocumentMetadata.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexEntry.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexEntry.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexEntry.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexEntry.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexParser.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexParser.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeIndexParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeSchemaProvider.cs b/src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeSchemaProvider.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeSchemaProvider.cs rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Internal/AdobeSchemaProvider.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Schemas/adobe-bulletin.schema.json b/src/StellaOps.Feedser.Source.Vndr.Adobe/Schemas/adobe-bulletin.schema.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/Schemas/adobe-bulletin.schema.json rename to src/StellaOps.Feedser.Source.Vndr.Adobe/Schemas/adobe-bulletin.schema.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj b/src/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj similarity index 88% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj rename to src/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj index 24fd34f9..89b5f59e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Adobe/StellaOps.Feedser.Source.Vndr.Adobe.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/TASKS.md b/src/StellaOps.Feedser.Source.Vndr.Adobe/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Adobe/TASKS.md rename to src/StellaOps.Feedser.Source.Vndr.Adobe/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Apple/Class1.cs b/src/StellaOps.Feedser.Source.Vndr.Apple/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Apple/Class1.cs rename to src/StellaOps.Feedser.Source.Vndr.Apple/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj b/src/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj rename to src/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Apple/StellaOps.Feedser.Source.Vndr.Apple.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumConnectorTests.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumConnectorTests.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumMapperTests.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumMapperTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumMapperTests.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/ChromiumMapperTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-advisory.snapshot.json b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-advisory.snapshot.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-advisory.snapshot.json rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-advisory.snapshot.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-detail.html b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-detail.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-detail.html rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-detail.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-feed.xml b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-feed.xml similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-feed.xml rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/Chromium/Fixtures/chromium-feed.xml diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj b/src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium.Tests/StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj rename to src/StellaOps.Feedser.Source.Vndr.Chromium.Tests/StellaOps.Feedser.Source.Vndr.Chromium.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/AGENTS.md b/src/StellaOps.Feedser.Source.Vndr.Chromium/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/AGENTS.md rename to src/StellaOps.Feedser.Source.Vndr.Chromium/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnector.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnector.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumDiagnostics.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumDiagnostics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumDiagnostics.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumDiagnostics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/ChromiumServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Configuration/ChromiumOptions.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Configuration/ChromiumOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Configuration/ChromiumOptions.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Configuration/ChromiumOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumCursor.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumCursor.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDocumentMetadata.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDocumentMetadata.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDocumentMetadata.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDocumentMetadata.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDto.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDto.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedEntry.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedEntry.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedEntry.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedEntry.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedLoader.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedLoader.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedLoader.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumFeedLoader.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumMapper.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumMapper.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumParser.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumParser.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumSchemaProvider.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumSchemaProvider.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumSchemaProvider.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Internal/ChromiumSchemaProvider.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Properties/AssemblyInfo.cs b/src/StellaOps.Feedser.Source.Vndr.Chromium/Properties/AssemblyInfo.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Properties/AssemblyInfo.cs rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Properties/AssemblyInfo.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Schemas/chromium-post.schema.json b/src/StellaOps.Feedser.Source.Vndr.Chromium/Schemas/chromium-post.schema.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/Schemas/chromium-post.schema.json rename to src/StellaOps.Feedser.Source.Vndr.Chromium/Schemas/chromium-post.schema.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj b/src/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj similarity index 91% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj rename to src/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj index bf487399..fca938f3 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Chromium/StellaOps.Feedser.Source.Vndr.Chromium.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/TASKS.md b/src/StellaOps.Feedser.Source.Vndr.Chromium/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Chromium/TASKS.md rename to src/StellaOps.Feedser.Source.Vndr.Chromium/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Cisco/Class1.cs b/src/StellaOps.Feedser.Source.Vndr.Cisco/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Cisco/Class1.cs rename to src/StellaOps.Feedser.Source.Vndr.Cisco/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj b/src/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj rename to src/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Cisco/StellaOps.Feedser.Source.Vndr.Cisco.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Msrc/Class1.cs b/src/StellaOps.Feedser.Source.Vndr.Msrc/Class1.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Msrc/Class1.cs rename to src/StellaOps.Feedser.Source.Vndr.Msrc/Class1.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj b/src/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj similarity index 82% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj rename to src/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj index c9660811..182529d4 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Msrc/StellaOps.Feedser.Source.Vndr.Msrc.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-advisories.snapshot.json b/src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-advisories.snapshot.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-advisories.snapshot.json rename to src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-advisories.snapshot.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-01.html b/src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-01.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-01.html rename to src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-01.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-02.html b/src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-02.html similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-02.html rename to src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/Fixtures/oracle-detail-cpuapr2024-02.html diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/OracleConnectorTests.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/OracleConnectorTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/OracleConnectorTests.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/Oracle/OracleConnectorTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj b/src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle.Tests/StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj rename to src/StellaOps.Feedser.Source.Vndr.Oracle.Tests/StellaOps.Feedser.Source.Vndr.Oracle.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/AGENTS.md b/src/StellaOps.Feedser.Source.Vndr.Oracle/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/AGENTS.md rename to src/StellaOps.Feedser.Source.Vndr.Oracle/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Configuration/OracleOptions.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Configuration/OracleOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Configuration/OracleOptions.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Configuration/OracleOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleCursor.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleCursor.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDocumentMetadata.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDocumentMetadata.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDocumentMetadata.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDocumentMetadata.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDto.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDto.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleMapper.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleMapper.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleParser.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleParser.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleParser.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/Internal/OracleParser.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/OracleConnector.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/OracleConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/OracleConnector.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/OracleConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/OracleServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/OracleServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/OracleServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/OracleServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj b/src/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj similarity index 85% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj rename to src/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj index 68e91120..8b8fb3b3 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Oracle/StellaOps.Feedser.Source.Vndr.Oracle.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/TASKS.md b/src/StellaOps.Feedser.Source.Vndr.Oracle/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/TASKS.md rename to src/StellaOps.Feedser.Source.Vndr.Oracle/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/VndrOracleConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Vndr.Oracle/VndrOracleConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Oracle/VndrOracleConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Vndr.Oracle/VndrOracleConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware.Tests/StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj b/src/StellaOps.Feedser.Source.Vndr.Vmware.Tests/StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware.Tests/StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj rename to src/StellaOps.Feedser.Source.Vndr.Vmware.Tests/StellaOps.Feedser.Source.Vndr.Vmware.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware.Tests/Vmware/VmwareMapperTests.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware.Tests/Vmware/VmwareMapperTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware.Tests/Vmware/VmwareMapperTests.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware.Tests/Vmware/VmwareMapperTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/AGENTS.md b/src/StellaOps.Feedser.Source.Vndr.Vmware/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/AGENTS.md rename to src/StellaOps.Feedser.Source.Vndr.Vmware/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Configuration/VmwareOptions.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Configuration/VmwareOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Configuration/VmwareOptions.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Configuration/VmwareOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareCursor.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareCursor.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareCursor.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareCursor.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareDetailDto.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareDetailDto.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareDetailDto.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareDetailDto.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareIndexItem.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareIndexItem.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareIndexItem.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareIndexItem.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareMapper.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareMapper.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareMapper.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Internal/VmwareMapper.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Jobs.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/Jobs.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/Jobs.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/Jobs.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj b/src/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj similarity index 90% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj rename to src/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj index b7480a6e..08a26244 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj +++ b/src/StellaOps.Feedser.Source.Vndr.Vmware/StellaOps.Feedser.Source.Vndr.Vmware.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/TASKS.md b/src/StellaOps.Feedser.Source.Vndr.Vmware/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/TASKS.md rename to src/StellaOps.Feedser.Source.Vndr.Vmware/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnector.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnector.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnector.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnector.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnectorPlugin.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnectorPlugin.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnectorPlugin.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareConnectorPlugin.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareDependencyInjectionRoutine.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareDependencyInjectionRoutine.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareDependencyInjectionRoutine.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareDependencyInjectionRoutine.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareServiceCollectionExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Source.Vndr.Vmware/VmwareServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Source.Vndr.Vmware/VmwareServiceCollectionExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStorePerformanceTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStorePerformanceTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStorePerformanceTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStorePerformanceTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/AdvisoryStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/DocumentStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/DocumentStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/DocumentStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/DocumentStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/DtoStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/DtoStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/DtoStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/DtoStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateManagerTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateManagerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateManagerTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateManagerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/ExportStateStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MergeEventStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/MergeEventStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MergeEventStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/MergeEventStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/Migrations/MongoMigrationRunnerTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/Migrations/MongoMigrationRunnerTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/Migrations/MongoMigrationRunnerTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/Migrations/MongoMigrationRunnerTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MongoJobStoreTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/MongoJobStoreTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MongoJobStoreTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/MongoJobStoreTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MongoSourceStateRepositoryTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/MongoSourceStateRepositoryTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/MongoSourceStateRepositoryTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/MongoSourceStateRepositoryTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/RawDocumentRetentionServiceTests.cs b/src/StellaOps.Feedser.Storage.Mongo.Tests/RawDocumentRetentionServiceTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/RawDocumentRetentionServiceTests.cs rename to src/StellaOps.Feedser.Storage.Mongo.Tests/RawDocumentRetentionServiceTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/StellaOps.Feedser.Storage.Mongo.Tests.csproj b/src/StellaOps.Feedser.Storage.Mongo.Tests/StellaOps.Feedser.Storage.Mongo.Tests.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo.Tests/StellaOps.Feedser.Storage.Mongo.Tests.csproj rename to src/StellaOps.Feedser.Storage.Mongo.Tests/StellaOps.Feedser.Storage.Mongo.Tests.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/AGENTS.md b/src/StellaOps.Feedser.Storage.Mongo/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/AGENTS.md rename to src/StellaOps.Feedser.Storage.Mongo/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Advisories/AdvisoryStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/IAdvisoryStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Advisories/IAdvisoryStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Advisories/IAdvisoryStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Advisories/IAdvisoryStore.cs diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocument.cs new file mode 100644 index 00000000..446820b2 --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocument.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +[BsonIgnoreExtraElements] +public sealed class ChangeHistoryDocument +{ + [BsonId] + public Guid Id { get; set; } + + [BsonElement("source")] + public string SourceName { get; set; } = string.Empty; + + [BsonElement("advisoryKey")] + public string AdvisoryKey { get; set; } = string.Empty; + + [BsonElement("documentId")] + public Guid DocumentId { get; set; } + + [BsonElement("documentSha256")] + public string DocumentSha256 { get; set; } = string.Empty; + + [BsonElement("currentHash")] + public string CurrentHash { get; set; } = string.Empty; + + [BsonElement("previousHash")] + public string? PreviousHash { get; set; } + + [BsonElement("currentSnapshot")] + public string CurrentSnapshot { get; set; } = string.Empty; + + [BsonElement("previousSnapshot")] + public string? PreviousSnapshot { get; set; } + + [BsonElement("changes")] + public List Changes { get; set; } = new(); + + [BsonElement("capturedAt")] + public DateTime CapturedAt { get; set; } +} diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocumentExtensions.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocumentExtensions.cs new file mode 100644 index 00000000..3acaf472 --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryDocumentExtensions.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using MongoDB.Bson; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +internal static class ChangeHistoryDocumentExtensions +{ + public static ChangeHistoryDocument ToDocument(this ChangeHistoryRecord record) + { + var changes = new List(record.Changes.Count); + foreach (var change in record.Changes) + { + changes.Add(new BsonDocument + { + ["field"] = change.Field, + ["type"] = change.ChangeType, + ["previous"] = change.PreviousValue is null ? BsonNull.Value : new BsonString(change.PreviousValue), + ["current"] = change.CurrentValue is null ? BsonNull.Value : new BsonString(change.CurrentValue), + }); + } + + return new ChangeHistoryDocument + { + Id = record.Id, + SourceName = record.SourceName, + AdvisoryKey = record.AdvisoryKey, + DocumentId = record.DocumentId, + DocumentSha256 = record.DocumentSha256, + CurrentHash = record.CurrentHash, + PreviousHash = record.PreviousHash, + CurrentSnapshot = record.CurrentSnapshot, + PreviousSnapshot = record.PreviousSnapshot, + Changes = changes, + CapturedAt = record.CapturedAt.UtcDateTime, + }; + } + + public static ChangeHistoryRecord ToRecord(this ChangeHistoryDocument document) + { + var changes = new List(document.Changes.Count); + foreach (var change in document.Changes) + { + var previousValue = change.TryGetValue("previous", out var previousBson) && previousBson is not BsonNull + ? previousBson.AsString + : null; + var currentValue = change.TryGetValue("current", out var currentBson) && currentBson is not BsonNull + ? currentBson.AsString + : null; + var fieldName = change.GetValue("field", "").AsString; + var changeType = change.GetValue("type", "").AsString; + changes.Add(new ChangeHistoryFieldChange(fieldName, changeType, previousValue, currentValue)); + } + + var capturedAtUtc = DateTime.SpecifyKind(document.CapturedAt, DateTimeKind.Utc); + + return new ChangeHistoryRecord( + document.Id, + document.SourceName, + document.AdvisoryKey, + document.DocumentId, + document.DocumentSha256, + document.CurrentHash, + document.PreviousHash, + document.CurrentSnapshot, + document.PreviousSnapshot, + changes, + new DateTimeOffset(capturedAtUtc)); + } +} diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryFieldChange.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryFieldChange.cs new file mode 100644 index 00000000..cac29113 --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryFieldChange.cs @@ -0,0 +1,24 @@ +using System; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +public sealed record ChangeHistoryFieldChange +{ + public ChangeHistoryFieldChange(string field, string changeType, string? previousValue, string? currentValue) + { + ArgumentException.ThrowIfNullOrEmpty(field); + ArgumentException.ThrowIfNullOrEmpty(changeType); + Field = field; + ChangeType = changeType; + PreviousValue = previousValue; + CurrentValue = currentValue; + } + + public string Field { get; } + + public string ChangeType { get; } + + public string? PreviousValue { get; } + + public string? CurrentValue { get; } +} diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryRecord.cs new file mode 100644 index 00000000..8356ad36 --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/ChangeHistoryRecord.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +public sealed class ChangeHistoryRecord +{ + public ChangeHistoryRecord( + Guid id, + string sourceName, + string advisoryKey, + Guid documentId, + string documentSha256, + string currentHash, + string? previousHash, + string currentSnapshot, + string? previousSnapshot, + IReadOnlyList changes, + DateTimeOffset capturedAt) + { + ArgumentException.ThrowIfNullOrEmpty(sourceName); + ArgumentException.ThrowIfNullOrEmpty(advisoryKey); + ArgumentException.ThrowIfNullOrEmpty(documentSha256); + ArgumentException.ThrowIfNullOrEmpty(currentHash); + ArgumentException.ThrowIfNullOrEmpty(currentSnapshot); + ArgumentNullException.ThrowIfNull(changes); + + Id = id; + SourceName = sourceName; + AdvisoryKey = advisoryKey; + DocumentId = documentId; + DocumentSha256 = documentSha256; + CurrentHash = currentHash; + PreviousHash = previousHash; + CurrentSnapshot = currentSnapshot; + PreviousSnapshot = previousSnapshot; + Changes = changes; + CapturedAt = capturedAt; + } + + public Guid Id { get; } + + public string SourceName { get; } + + public string AdvisoryKey { get; } + + public Guid DocumentId { get; } + + public string DocumentSha256 { get; } + + public string CurrentHash { get; } + + public string? PreviousHash { get; } + + public string CurrentSnapshot { get; } + + public string? PreviousSnapshot { get; } + + public IReadOnlyList Changes { get; } + + public DateTimeOffset CapturedAt { get; } +} diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/IChangeHistoryStore.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/IChangeHistoryStore.cs new file mode 100644 index 00000000..c1e0df4a --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/IChangeHistoryStore.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +public interface IChangeHistoryStore +{ + Task AddAsync(ChangeHistoryRecord record, CancellationToken cancellationToken); + + Task> GetRecentAsync(string sourceName, string advisoryKey, int limit, CancellationToken cancellationToken); +} diff --git a/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/MongoChangeHistoryStore.cs b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/MongoChangeHistoryStore.cs new file mode 100644 index 00000000..8fc9e0ea --- /dev/null +++ b/src/StellaOps.Feedser.Storage.Mongo/ChangeHistory/MongoChangeHistoryStore.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; + +namespace StellaOps.Feedser.Storage.Mongo.ChangeHistory; + +public sealed class MongoChangeHistoryStore : IChangeHistoryStore +{ + private readonly IMongoCollection _collection; + private readonly ILogger _logger; + + public MongoChangeHistoryStore(IMongoDatabase database, ILogger logger) + { + _collection = (database ?? throw new ArgumentNullException(nameof(database))) + .GetCollection(MongoStorageDefaults.Collections.ChangeHistory); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task AddAsync(ChangeHistoryRecord record, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(record); + var document = record.ToDocument(); + await _collection.InsertOneAsync(document, cancellationToken: cancellationToken).ConfigureAwait(false); + _logger.LogDebug("Recorded change history for {Source}/{Advisory} with hash {Hash}", record.SourceName, record.AdvisoryKey, record.CurrentHash); + } + + public async Task> GetRecentAsync(string sourceName, string advisoryKey, int limit, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(sourceName); + ArgumentException.ThrowIfNullOrEmpty(advisoryKey); + if (limit <= 0) + { + limit = 10; + } + + var cursor = await _collection.Find(x => x.SourceName == sourceName && x.AdvisoryKey == advisoryKey) + .SortByDescending(x => x.CapturedAt) + .Limit(limit) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + + var records = new List(cursor.Count); + foreach (var document in cursor) + { + records.Add(document.ToRecord()); + } + + return records; + } +} diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/DocumentStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Documents/DocumentStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/IDocumentStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Documents/IDocumentStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Documents/IDocumentStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Documents/IDocumentStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/DtoStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Dtos/DtoStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/IDtoStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Dtos/IDtoStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Dtos/IDtoStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Dtos/IDtoStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateManager.cs b/src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateManager.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateManager.cs rename to src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateManager.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Exporting/ExportStateStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/IExportStateStore.cs b/src/StellaOps.Feedser.Storage.Mongo/Exporting/IExportStateStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Exporting/IExportStateStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/Exporting/IExportStateStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/ISourceStateRepository.cs b/src/StellaOps.Feedser.Storage.Mongo/ISourceStateRepository.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/ISourceStateRepository.cs rename to src/StellaOps.Feedser.Storage.Mongo/ISourceStateRepository.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JobLeaseDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/JobLeaseDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JobLeaseDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/JobLeaseDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JobRunDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/JobRunDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JobRunDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/JobRunDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/IJpFlagStore.cs b/src/StellaOps.Feedser.Storage.Mongo/JpFlags/IJpFlagStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/IJpFlagStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/JpFlags/IJpFlagStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagStore.cs b/src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/JpFlags/JpFlagStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MIGRATIONS.md b/src/StellaOps.Feedser.Storage.Mongo/MIGRATIONS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MIGRATIONS.md rename to src/StellaOps.Feedser.Storage.Mongo/MIGRATIONS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/IMergeEventStore.cs b/src/StellaOps.Feedser.Storage.Mongo/MergeEvents/IMergeEventStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/IMergeEventStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/MergeEvents/IMergeEventStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventStore.cs b/src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/MergeEvents/MergeEventStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureDocumentExpiryIndexesMigration.cs b/src/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureDocumentExpiryIndexesMigration.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureDocumentExpiryIndexesMigration.cs rename to src/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureDocumentExpiryIndexesMigration.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureGridFsExpiryIndexesMigration.cs b/src/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureGridFsExpiryIndexesMigration.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureGridFsExpiryIndexesMigration.cs rename to src/StellaOps.Feedser.Storage.Mongo/Migrations/EnsureGridFsExpiryIndexesMigration.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/IMongoMigration.cs b/src/StellaOps.Feedser.Storage.Mongo/Migrations/IMongoMigration.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/IMongoMigration.cs rename to src/StellaOps.Feedser.Storage.Mongo/Migrations/IMongoMigration.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationRunner.cs b/src/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationRunner.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationRunner.cs rename to src/StellaOps.Feedser.Storage.Mongo/Migrations/MongoMigrationRunner.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs similarity index 92% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs index 7e80fa2b..9ea86565 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs +++ b/src/StellaOps.Feedser.Storage.Mongo/MongoBootstrapper.cs @@ -28,6 +28,7 @@ public sealed class MongoBootstrapper MongoStorageDefaults.Collections.PsirtFlags, MongoStorageDefaults.Collections.MergeEvent, MongoStorageDefaults.Collections.ExportState, + MongoStorageDefaults.Collections.ChangeHistory, MongoStorageDefaults.Collections.Locks, MongoStorageDefaults.Collections.Jobs, MongoStorageDefaults.Collections.Migrations, @@ -74,6 +75,7 @@ public sealed class MongoBootstrapper EnsureReferenceIndexesAsync(cancellationToken), EnsureSourceStateIndexesAsync(cancellationToken), EnsurePsirtFlagIndexesAsync(cancellationToken), + EnsureChangeHistoryIndexesAsync(cancellationToken), EnsureGridFsIndexesAsync(cancellationToken)).ConfigureAwait(false); await _migrationRunner.RunAsync(cancellationToken).ConfigureAwait(false); @@ -282,4 +284,23 @@ public sealed class MongoBootstrapper return collection.Indexes.CreateManyAsync(indexes, cancellationToken); } + + private Task EnsureChangeHistoryIndexesAsync(CancellationToken cancellationToken) + { + var collection = _database.GetCollection(MongoStorageDefaults.Collections.ChangeHistory); + var indexes = new List> + { + new( + Builders.IndexKeys.Ascending("source").Ascending("advisoryKey").Descending("capturedAt"), + new CreateIndexOptions { Name = "history_source_advisory_capturedAt" }), + new( + Builders.IndexKeys.Descending("capturedAt"), + new CreateIndexOptions { Name = "history_capturedAt" }), + new( + Builders.IndexKeys.Ascending("documentId"), + new CreateIndexOptions { Name = "history_documentId" }) + }; + + return collection.Indexes.CreateManyAsync(indexes, cancellationToken); + } } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoJobStore.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoJobStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoJobStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoJobStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoLeaseStore.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoLeaseStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoLeaseStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoLeaseStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoSourceStateRepository.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoSourceStateRepository.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoSourceStateRepository.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoSourceStateRepository.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs similarity index 93% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs index 22d5fa61..db8d636e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs +++ b/src/StellaOps.Feedser.Storage.Mongo/MongoStorageDefaults.cs @@ -23,5 +23,6 @@ public static class MongoStorageDefaults public const string Locks = "locks"; public const string Jobs = "jobs"; public const string Migrations = "schema_migrations"; + public const string ChangeHistory = "source_change_history"; } } diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoStorageOptions.cs b/src/StellaOps.Feedser.Storage.Mongo/MongoStorageOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/MongoStorageOptions.cs rename to src/StellaOps.Feedser.Storage.Mongo/MongoStorageOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Properties/AssemblyInfo.cs b/src/StellaOps.Feedser.Storage.Mongo/Properties/AssemblyInfo.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/Properties/AssemblyInfo.cs rename to src/StellaOps.Feedser.Storage.Mongo/Properties/AssemblyInfo.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/IPsirtFlagStore.cs b/src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/IPsirtFlagStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/IPsirtFlagStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/IPsirtFlagStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagStore.cs b/src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagStore.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagStore.cs rename to src/StellaOps.Feedser.Storage.Mongo/PsirtFlags/PsirtFlagStore.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/RawDocumentRetentionService.cs b/src/StellaOps.Feedser.Storage.Mongo/RawDocumentRetentionService.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/RawDocumentRetentionService.cs rename to src/StellaOps.Feedser.Storage.Mongo/RawDocumentRetentionService.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs b/src/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs similarity index 96% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs rename to src/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs index 88abd50a..3ec0f89e 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs +++ b/src/StellaOps.Feedser.Storage.Mongo/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using MongoDB.Driver; using StellaOps.Feedser.Core.Jobs; using StellaOps.Feedser.Storage.Mongo.Advisories; +using StellaOps.Feedser.Storage.Mongo.ChangeHistory; using StellaOps.Feedser.Storage.Mongo.Documents; using StellaOps.Feedser.Storage.Mongo.Dtos; using StellaOps.Feedser.Storage.Mongo.Exporting; @@ -57,6 +58,7 @@ public static class ServiceCollectionExtensions services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateDocument.cs b/src/StellaOps.Feedser.Storage.Mongo/SourceStateDocument.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateDocument.cs rename to src/StellaOps.Feedser.Storage.Mongo/SourceStateDocument.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateRecord.cs b/src/StellaOps.Feedser.Storage.Mongo/SourceStateRecord.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateRecord.cs rename to src/StellaOps.Feedser.Storage.Mongo/SourceStateRecord.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateRepositoryExtensions.cs b/src/StellaOps.Feedser.Storage.Mongo/SourceStateRepositoryExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/SourceStateRepositoryExtensions.cs rename to src/StellaOps.Feedser.Storage.Mongo/SourceStateRepositoryExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/StellaOps.Feedser.Storage.Mongo.csproj b/src/StellaOps.Feedser.Storage.Mongo/StellaOps.Feedser.Storage.Mongo.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/StellaOps.Feedser.Storage.Mongo.csproj rename to src/StellaOps.Feedser.Storage.Mongo/StellaOps.Feedser.Storage.Mongo.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/TASKS.md b/src/StellaOps.Feedser.Storage.Mongo/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Storage.Mongo/TASKS.md rename to src/StellaOps.Feedser.Storage.Mongo/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Testing/ConnectorTestHarness.cs b/src/StellaOps.Feedser.Testing/ConnectorTestHarness.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Testing/ConnectorTestHarness.cs rename to src/StellaOps.Feedser.Testing/ConnectorTestHarness.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Testing/MongoIntegrationFixture.cs b/src/StellaOps.Feedser.Testing/MongoIntegrationFixture.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Testing/MongoIntegrationFixture.cs rename to src/StellaOps.Feedser.Testing/MongoIntegrationFixture.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Testing/StellaOps.Feedser.Testing.csproj b/src/StellaOps.Feedser.Testing/StellaOps.Feedser.Testing.csproj similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Testing/StellaOps.Feedser.Testing.csproj rename to src/StellaOps.Feedser.Testing/StellaOps.Feedser.Testing.csproj diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Tests.Shared/AssemblyInfo.cs b/src/StellaOps.Feedser.Tests.Shared/AssemblyInfo.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Tests.Shared/AssemblyInfo.cs rename to src/StellaOps.Feedser.Tests.Shared/AssemblyInfo.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Tests.Shared/MongoFixtureCollection.cs b/src/StellaOps.Feedser.Tests.Shared/MongoFixtureCollection.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.Tests.Shared/MongoFixtureCollection.cs rename to src/StellaOps.Feedser.Tests.Shared/MongoFixtureCollection.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/PluginLoaderTests.cs b/src/StellaOps.Feedser.WebService.Tests/PluginLoaderTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/PluginLoaderTests.cs rename to src/StellaOps.Feedser.WebService.Tests/PluginLoaderTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj b/src/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj similarity index 85% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj rename to src/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj index fc2954ad..739ea8e7 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj +++ b/src/StellaOps.Feedser.WebService.Tests/StellaOps.Feedser.WebService.Tests.csproj @@ -8,6 +8,6 @@ - + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs b/src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs rename to src/StellaOps.Feedser.WebService.Tests/WebServiceEndpointsTests.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/AGENTS.md b/src/StellaOps.Feedser.WebService/AGENTS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/AGENTS.md rename to src/StellaOps.Feedser.WebService/AGENTS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/HealthContracts.cs b/src/StellaOps.Feedser.WebService/Diagnostics/HealthContracts.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/HealthContracts.cs rename to src/StellaOps.Feedser.WebService/Diagnostics/HealthContracts.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/JobMetrics.cs b/src/StellaOps.Feedser.WebService/Diagnostics/JobMetrics.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/JobMetrics.cs rename to src/StellaOps.Feedser.WebService/Diagnostics/JobMetrics.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/ProblemTypes.cs b/src/StellaOps.Feedser.WebService/Diagnostics/ProblemTypes.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/ProblemTypes.cs rename to src/StellaOps.Feedser.WebService/Diagnostics/ProblemTypes.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/ServiceStatus.cs b/src/StellaOps.Feedser.WebService/Diagnostics/ServiceStatus.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Diagnostics/ServiceStatus.cs rename to src/StellaOps.Feedser.WebService/Diagnostics/ServiceStatus.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/ConfigurationExtensions.cs b/src/StellaOps.Feedser.WebService/Extensions/ConfigurationExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/ConfigurationExtensions.cs rename to src/StellaOps.Feedser.WebService/Extensions/ConfigurationExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/JobRegistrationExtensions.cs b/src/StellaOps.Feedser.WebService/Extensions/JobRegistrationExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/JobRegistrationExtensions.cs rename to src/StellaOps.Feedser.WebService/Extensions/JobRegistrationExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/TelemetryExtensions.cs b/src/StellaOps.Feedser.WebService/Extensions/TelemetryExtensions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Extensions/TelemetryExtensions.cs rename to src/StellaOps.Feedser.WebService/Extensions/TelemetryExtensions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobDefinitionResponse.cs b/src/StellaOps.Feedser.WebService/Jobs/JobDefinitionResponse.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobDefinitionResponse.cs rename to src/StellaOps.Feedser.WebService/Jobs/JobDefinitionResponse.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobRunResponse.cs b/src/StellaOps.Feedser.WebService/Jobs/JobRunResponse.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobRunResponse.cs rename to src/StellaOps.Feedser.WebService/Jobs/JobRunResponse.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobTriggerRequest.cs b/src/StellaOps.Feedser.WebService/Jobs/JobTriggerRequest.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Jobs/JobTriggerRequest.cs rename to src/StellaOps.Feedser.WebService/Jobs/JobTriggerRequest.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Options/FeedserOptions.cs b/src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Options/FeedserOptions.cs rename to src/StellaOps.Feedser.WebService/Options/FeedserOptions.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs b/src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs rename to src/StellaOps.Feedser.WebService/Options/FeedserOptionsValidator.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Program.cs b/src/StellaOps.Feedser.WebService/Program.cs similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Program.cs rename to src/StellaOps.Feedser.WebService/Program.cs diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/Properties/launchSettings.json b/src/StellaOps.Feedser.WebService/Properties/launchSettings.json similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/Properties/launchSettings.json rename to src/StellaOps.Feedser.WebService/Properties/launchSettings.json diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj b/src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj similarity index 89% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj rename to src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj index f31fd4fd..cc8f5866 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj +++ b/src/StellaOps.Feedser.WebService/StellaOps.Feedser.WebService.csproj @@ -25,7 +25,7 @@ - - + + diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.WebService/TASKS.md b/src/StellaOps.Feedser.WebService/TASKS.md similarity index 100% rename from src/StellaOps.Feedser/StellaOps.Feedser.WebService/TASKS.md rename to src/StellaOps.Feedser.WebService/TASKS.md diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.sln b/src/StellaOps.Feedser.sln similarity index 99% rename from src/StellaOps.Feedser/StellaOps.Feedser.sln rename to src/StellaOps.Feedser.sln index 6ebdfa46..c542ef93 100644 --- a/src/StellaOps.Feedser/StellaOps.Feedser.sln +++ b/src/StellaOps.Feedser.sln @@ -23,7 +23,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Exporter.TrivyDb", "StellaOps.Feedser.Exporter.TrivyDb\StellaOps.Feedser.Exporter.TrivyDb.csproj", "{4D936BC4-5520-4642-A237-4106E97BC7A0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Plugin", "..\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj", "{B85C1C0E-B245-44FB-877E-C112DE29041A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Plugin", "StellaOps.Plugin\StellaOps.Plugin.csproj", "{B85C1C0E-B245-44FB-877E-C112DE29041A}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.WebService", "StellaOps.Feedser.WebService\StellaOps.Feedser.WebService.csproj", "{2C970A0F-FE3D-425B-B1B3-A008B194F5C2}" EndProject @@ -79,7 +79,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Gh EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Source.Distro.RedHat", "StellaOps.Feedser.Source.Distro.RedHat\StellaOps.Feedser.Source.Distro.RedHat.csproj", "{A4DBF88F-34D0-4A05-ACCE-DE080F912FDB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.DependencyInjection", "..\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj", "{F622D38D-DA49-473E-B724-E706F8113CF2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.DependencyInjection", "StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj", "{F622D38D-DA49-473E-B724-E706F8113CF2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Feedser.Core.Tests", "StellaOps.Feedser.Core.Tests\StellaOps.Feedser.Core.Tests.csproj", "{3A3D7610-C864-4413-B07E-9E8C2A49A90E}" EndProject diff --git a/src/StellaOps.Feedser/AGENTS.md b/src/StellaOps.Feedser/AGENTS.md deleted file mode 100644 index 2ec4c320..00000000 --- a/src/StellaOps.Feedser/AGENTS.md +++ /dev/null @@ -1,129 +0,0 @@ -## Autonomous Agent Instructions - -Before you act you need to read `/src/StellaOps.Feedser/AGENTS.md` files `AGENTS.md`,`TASKS.md` in each working directory I gave you. - -Boundaries: -- You operate only in the working directories I gave you, unless there is dependencies that makes you to work on dependency in shared directory. Then you ask for confirmation. - -Do: -- Keep endpoints small, deterministic, and cancellation-aware. -- Improve logs/metrics as per tasks. -- Update `TASKS.md` when moving tasks forward. -- When you are done with all task you state explicitly you are done. -- Impersonate the role described on working directory `AGENTS.md` you will read, if role is not available - take role of the CTO of the StellaOps in early stages. - -Output: -- Summary of changes and any cross-module requests. - -# StellaOps — Agent Operations Guide (Master) - -> Purpose: Orient all human + autonomous agents to the StellaOps platform, its data flows, component boundaries, and rules of engagement so teams can work **in parallel on the same branch** with minimal contention. - ---- - -## 1) What is StellaOps? - -**StellaOps** is a sovereign/offline-first container & infrastructure security platform. Its core loop: - -1. **Intelligence** — Ingest vulnerability advisories from primary sources. -2. **Normalization & Merge** — Reconcile into a canonical, deduplicated database with deterministic precedence. -3. **Distribution** — Export a Trivy-compatible database (OCI artifact) and optional vuln-list JSON for self-hosted scanners. -4. **Scanning & Policy** — Scanners consume that DB; policy engines gate deployments; artifacts may be signed downstream. - -This repository’s focus is the **Feedser** service (ingest/merge/export). - ---- - -## 2) High‑level architecture (technical overview) - -``` - -``` - [Primary Sources: NVD, GHSA/OSV, Distros, PSIRTs, CERTs, KEV, ICS] - │ - (Fetch + Validate DTOs) - ▼ - [Normalizer → Canonical Advisory] - │ - (Alias graph + Precedence) - ▼ - [Feed‑Merge Store (MongoDB)] - │ - ┌──────────────┴──────────────┐ - ▼ ▼ -[Export: vuln‑list JSON] [Packager: Trivy DB (OCI)] - │ │ - └────────────► Distribution (ORAS / offline bundle) -``` - -``` - -**Key invariants** - -- **Deterministic**: same inputs → same canonical JSON → same export digests. -- **Precedence**: **distro OVAL/PSIRT > NVD** for OS packages; **KEV only flags exploitation**; regional CERTs enrich text/refs. -- **Provenance** everywhere: source document, extraction method (`parser|llm`), and timestamps. - -You have to read `./ARCHITECTURE.md` for more information. ---- - -## 4) Main agents (roles, interactions, scope) - -- **BE‑Base (Platform & Pipeline)** - Owns DI, plugin host, job scheduler/coordinator, configuration binding, minimal API endpoints, and Mongo bootstrapping. -- **BE‑Conn‑X (Connectors)** - One agent per source family (NVD, Red Hat, Ubuntu, Debian, SUSE, GHSA, OSV, PSIRTs, CERTs, KEV, ICS). Implements fetch/parse/map with incremental watermarks. -- **BE‑Merge (Canonical Merge & Dedupe)** - Identity graph, precedence policies, canonical JSON serializer, and deterministic hashing (`merge_event`). -- **BE‑Export (JSON & Trivy DB)** - Deterministic export trees, Trivy DB packaging, optional ORAS push, and offline bundle. -- **QA (Validation & Observability)** - Schema tests, fixture goldens, determinism checks, metrics/logs/traces, e2e reproducibility runs. -- **DevEx/Docs** - Maintains this agent framework, templates, and per‑directory guides; assists parallelization and reviews. - -**Interaction sketch** - -- Connectors → **Core** jobs → **Storage.Mongo** -- **Merge** refines canonical records; **Exporters** read canonical data to produce artifacts -- **QA** spans all layers with fixtures/metrics and determinism checks - ---- - -## 5) Key technologies & integrations - -- **Runtime**: .NET 10 (`net10.0`) preview SDK; C# latest preview features. -- **Data**: MongoDB (canonical store and job/export state). -- **Packaging**: Trivy DB (BoltDB inside `db.tar.gz`), vuln‑list JSON (optional), ORAS for OCI push. -- **Observability**: structured logs, counters, and (optional) OpenTelemetry traces. -- **Ops posture**: offline‑first, allowlist for remote hosts, strict schema validation, gated LLM fallback (only where explicitly configured). - ---- - -## 6) Data flow (end‑to‑end) - -1. **Fetch**: connectors request source windows with retries/backoff, persist raw documents with SHA256/ETag metadata. -2. **Parse**: validate to DTOs (schema‑checked), quarantine failures. -3. **Map**: normalize to canonical advisories (aliases, affected ranges with NEVRA/EVR/SemVer, references, provenance). -4. **Merge**: enforce precedence and determinism; track before/after hashes. -5. **Export**: JSON tree and/or Trivy DB; package and (optionally) push; write export state. - ---- - -## 7) Work-in-parallel rules (important) - -- **Directory ownership**: Each agent works **only inside its module directory**. Cross‑module edits require a brief handshake in issues/PR description. -- **Scoping**: Use each module’s `AGENTS.md` and `TASKS.md` to plan; autonomous agents must read `src/AGENTS.md` and the module docs before acting. -- **Determinism**: Sort keys, normalize timestamps to UTC ISO‑8601, avoid non‑deterministic data in exports and tests. -- **Status tracking**: Update your module’s `TASKS.md` as you progress (TODO → DOING → DONE/BLOCKED). -- **Tests**: Add/extend fixtures and unit tests per change; never regress determinism or precedence. -- **Test layout**: Use module-specific projects in `StellaOps.Feedser..Tests`; shared fixtures/harnesses live in `StellaOps.Feedser.Testing`. - ---- - -## 8) Glossary (quick) - -- **OVAL** — Vendor/distro security definition format; authoritative for OS packages. -- **NEVRA / EVR** — RPM and Debian version semantics for OS packages. -- **PURL / SemVer** — Coordinates and version semantics for OSS ecosystems. -- **KEV** — Known Exploited Vulnerabilities (flag only). \ No newline at end of file diff --git a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs b/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs deleted file mode 100644 index ae832c7a..00000000 --- a/src/StellaOps.Feedser/StellaOps.Feedser.Exporter.TrivyDb.Tests/TrivyDbFeedExporterTests.cs +++ /dev/null @@ -1,312 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Security.Cryptography; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using StellaOps.Feedser.Exporter.Json; -using StellaOps.Feedser.Exporter.TrivyDb; -using StellaOps.Feedser.Models; -using StellaOps.Feedser.Storage.Mongo.Advisories; -using StellaOps.Feedser.Storage.Mongo.Exporting; - -namespace StellaOps.Feedser.Exporter.TrivyDb.Tests; - -public sealed class TrivyDbFeedExporterTests : IDisposable -{ - private readonly string _root; - private readonly string _jsonRoot; - - public TrivyDbFeedExporterTests() - { - _root = Directory.CreateTempSubdirectory("feedser-trivy-exporter-tests").FullName; - _jsonRoot = Path.Combine(_root, "tree"); - } - - [Fact] - public void ExportOptions_GetExportRoot_NormalizesRelativeRoot() - { - var options = new TrivyDbExportOptions - { - OutputRoot = Path.Combine("..", "exports", "trivy-test"), - }; - - var exportId = "20240901T000000Z"; - var path = options.GetExportRoot(exportId); - - Assert.True(Path.IsPathRooted(path)); - Assert.EndsWith(Path.Combine("exports", "trivy-test", exportId), path, StringComparison.Ordinal); - } - - [Fact] - public async Task ExportAsync_PersistsStateAndSkipsWhenDigestUnchanged() - { - var advisory = CreateSampleAdvisory(); - var advisoryStore = new StubAdvisoryStore(advisory); - - var optionsValue = new TrivyDbExportOptions - { - OutputRoot = _root, - ReferencePrefix = "example/trivy", - Json = new JsonExportOptions - { - OutputRoot = _jsonRoot, - MaintainLatestSymlink = false, - }, - KeepWorkingTree = false, - }; - - var options = Options.Create(optionsValue); - var packageBuilder = new TrivyDbPackageBuilder(); - var ociWriter = new TrivyDbOciWriter(); - var planner = new TrivyDbExportPlanner(); - var stateStore = new InMemoryExportStateStore(); - var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-09-01T00:00:00Z", CultureInfo.InvariantCulture)); - var stateManager = new ExportStateManager(stateStore, timeProvider); - var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new - { - Version = 2, - NextUpdate = "2024-09-02T00:00:00Z", - UpdatedAt = "2024-09-01T00:00:00Z", - }); - var builder = new StubTrivyDbBuilder(_root, builderMetadata); - var orasPusher = new StubTrivyDbOrasPusher(); - var exporter = new TrivyDbFeedExporter( - advisoryStore, - new VulnListJsonExportPathResolver(), - options, - packageBuilder, - ociWriter, - stateManager, - planner, - builder, - orasPusher, - NullLogger.Instance, - timeProvider); - - using var provider = new ServiceCollection().BuildServiceProvider(); - await exporter.ExportAsync(provider, CancellationToken.None); - - var record = await stateStore.FindAsync(TrivyDbFeedExporter.ExporterId, CancellationToken.None); - Assert.NotNull(record); - Assert.Equal("20240901T000000Z", record!.BaseExportId); - Assert.Equal(record.LastFullDigest, record.ExportCursor); - - var firstExportDirectory = Path.Combine(_root, record.BaseExportId); - Assert.True(Directory.Exists(firstExportDirectory)); - - timeProvider.Advance(TimeSpan.FromMinutes(5)); - await exporter.ExportAsync(provider, CancellationToken.None); - - var updatedRecord = await stateStore.FindAsync(TrivyDbFeedExporter.ExporterId, CancellationToken.None); - Assert.NotNull(updatedRecord); - Assert.Equal(record.UpdatedAt, updatedRecord!.UpdatedAt); - Assert.Equal(record.LastFullDigest, updatedRecord.LastFullDigest); - - var skippedExportDirectory = Path.Combine(_root, "20240901T000500Z"); - Assert.False(Directory.Exists(skippedExportDirectory)); - - Assert.Empty(orasPusher.Pushes); - } - - [Fact] - public async Task ExportAsync_CreatesOfflineBundle() - { - var advisory = CreateSampleAdvisory(); - var advisoryStore = new StubAdvisoryStore(advisory); - - var optionsValue = new TrivyDbExportOptions - { - OutputRoot = _root, - ReferencePrefix = "example/trivy", - Json = new JsonExportOptions - { - OutputRoot = _jsonRoot, - MaintainLatestSymlink = false, - }, - KeepWorkingTree = false, - OfflineBundle = new TrivyDbOfflineBundleOptions - { - Enabled = true, - FileName = "{exportId}.bundle.tar.gz", - }, - }; - - var options = Options.Create(optionsValue); - var packageBuilder = new TrivyDbPackageBuilder(); - var ociWriter = new TrivyDbOciWriter(); - var planner = new TrivyDbExportPlanner(); - var stateStore = new InMemoryExportStateStore(); - var timeProvider = new TestTimeProvider(DateTimeOffset.Parse("2024-09-15T00:00:00Z", CultureInfo.InvariantCulture)); - var stateManager = new ExportStateManager(stateStore, timeProvider); - var builderMetadata = JsonSerializer.SerializeToUtf8Bytes(new - { - Version = 2, - NextUpdate = "2024-09-16T00:00:00Z", - UpdatedAt = "2024-09-15T00:00:00Z", - }); - var builder = new StubTrivyDbBuilder(_root, builderMetadata); - var orasPusher = new StubTrivyDbOrasPusher(); - var exporter = new TrivyDbFeedExporter( - advisoryStore, - new VulnListJsonExportPathResolver(), - options, - packageBuilder, - ociWriter, - stateManager, - planner, - builder, - orasPusher, - NullLogger.Instance, - timeProvider); - - using var provider = new ServiceCollection().BuildServiceProvider(); - await exporter.ExportAsync(provider, CancellationToken.None); - - var exportId = "20240915T000000Z"; - var bundlePath = Path.Combine(_root, $"{exportId}.bundle.tar.gz"); - Assert.True(File.Exists(bundlePath)); - Assert.Empty(orasPusher.Pushes); - } - - private static Advisory CreateSampleAdvisory() - { - return new Advisory( - advisoryKey: "CVE-2024-9999", - title: "Trivy Export Test", - summary: null, - language: "en", - published: DateTimeOffset.Parse("2024-08-01T00:00:00Z", CultureInfo.InvariantCulture), - modified: DateTimeOffset.Parse("2024-08-02T00:00:00Z", CultureInfo.InvariantCulture), - severity: "medium", - exploitKnown: false, - aliases: new[] { "CVE-2024-9999" }, - references: Array.Empty(), - affectedPackages: Array.Empty(), - cvssMetrics: Array.Empty(), - provenance: Array.Empty()); - } - - public void Dispose() - { - try - { - if (Directory.Exists(_root)) - { - Directory.Delete(_root, recursive: true); - } - } - catch - { - // best effort cleanup - } - } - - private sealed class StubAdvisoryStore : IAdvisoryStore - { - private readonly IReadOnlyList _advisories; - - public StubAdvisoryStore(params Advisory[] advisories) - { - _advisories = advisories; - } - - public Task> GetRecentAsync(int limit, CancellationToken cancellationToken) - => Task.FromResult(_advisories); - - public Task FindAsync(string advisoryKey, CancellationToken cancellationToken) - => Task.FromResult(_advisories.FirstOrDefault(a => a.AdvisoryKey == advisoryKey)); - - public Task UpsertAsync(Advisory advisory, CancellationToken cancellationToken) - => Task.CompletedTask; - - public IAsyncEnumerable StreamAsync(CancellationToken cancellationToken) - { - return EnumerateAsync(cancellationToken); - - async IAsyncEnumerable EnumerateAsync(CancellationToken ct) - { - foreach (var advisory in _advisories) - { - ct.ThrowIfCancellationRequested(); - yield return advisory; - await Task.Yield(); - } - } - } - } - - private sealed class InMemoryExportStateStore : IExportStateStore - { - private ExportStateRecord? _record; - - public Task FindAsync(string id, CancellationToken cancellationToken) - => Task.FromResult(_record); - - public Task UpsertAsync(ExportStateRecord record, CancellationToken cancellationToken) - { - _record = record; - return Task.FromResult(record); - } - } - - private sealed class TestTimeProvider : TimeProvider - { - private DateTimeOffset _now; - - public TestTimeProvider(DateTimeOffset start) => _now = start; - - public override DateTimeOffset GetUtcNow() => _now; - - public void Advance(TimeSpan delta) => _now = _now.Add(delta); - } - - private sealed class StubTrivyDbBuilder : ITrivyDbBuilder - { - private readonly string _root; - private readonly byte[] _metadata; - - public StubTrivyDbBuilder(string root, byte[] metadata) - { - _root = root; - _metadata = metadata; - } - - public Task BuildAsync( - JsonExportResult jsonTree, - DateTimeOffset exportedAt, - string exportId, - CancellationToken cancellationToken) - { - var workingDirectory = Directory.CreateDirectory(Path.Combine(_root, $"builder-{exportId}")).FullName; - var archivePath = Path.Combine(workingDirectory, "db.tar.gz"); - var payload = new byte[] { 0x1, 0x2, 0x3, 0x4 }; - File.WriteAllBytes(archivePath, payload); - using var sha256 = SHA256.Create(); - var digest = "sha256:" + Convert.ToHexString(sha256.ComputeHash(payload)).ToLowerInvariant(); - var length = payload.Length; - - return Task.FromResult(new TrivyDbBuilderResult( - archivePath, - digest, - length, - _metadata, - workingDirectory)); - } - } - - private sealed class StubTrivyDbOrasPusher : ITrivyDbOrasPusher - { - public List<(string Layout, string Reference, string ExportId)> Pushes { get; } = new(); - - public Task PushAsync(string layoutPath, string reference, string exportId, CancellationToken cancellationToken) - { - Pushes.Add((layoutPath, reference, exportId)); - return Task.CompletedTask; - } - } -} diff --git a/src/StellaOps.Feedser/TASKS.md b/src/StellaOps.Feedser/TASKS.md deleted file mode 100644 index 280249f3..00000000 --- a/src/StellaOps.Feedser/TASKS.md +++ /dev/null @@ -1,9 +0,0 @@ -# TASKS -| Task | Owner(s) | Depends on | Notes | -|---|---|---|---| -|Document missing AGENTS/TASKS for remaining connectors|DevEx/Docs|Source leads|TODO – create `AGENTS.md`/`TASKS.md` for Acsc, Cccs, CertBund, CertCc, Cve, Debian, Suse, Ubuntu, Ghsa, Ics.Cisa, Kev, Kisa, Ru.Bdu, Ru.Nkcki, Vndr.Apple, Vndr.Cisco, Vndr.Msrc.| -|Connector option wiring audit|BE-Conn-Shared|Source.Common|TODO – ensure each connector exposes Options (base URLs, allowlisted HttpClient) similar to implemented modules.| -|Russian source schema + LLM fallback|BE-Conn-RU|Source.Ru.Bdu, Source.Ru.Nkcki|TODO – match HTML against XML schema; on failure/PDF use configured OpenAI/DeepDive/Qwen API with prompts, honoring observability + config gating.| -|Consolidate sample tests|QA|Tests|DONE – test suites now live under `StellaOps.Feedser..Tests` with shared fixtures in `StellaOps.Feedser.Testing`.| -|Adopt SourceDiagnostics beyond fetch stage|BE-Conn-Shared|Source.Common|TODO – emit spans/counters for parse/map phases so downstream telemetry matches new fetch instrumentation.| -|Stabilize exporter test suite (Json parity + Trivy bundle)|QA|Tests|TODO – resolve lingering build failures in parity smoke + Trivy tests and re-enable targeted test runs.| diff --git a/src/__Libraries/StellaOps.Plugin/DependencyInjection/PluginDependencyInjectionExtensions.cs b/src/StellaOps.Plugin/DependencyInjection/PluginDependencyInjectionExtensions.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/DependencyInjection/PluginDependencyInjectionExtensions.cs rename to src/StellaOps.Plugin/DependencyInjection/PluginDependencyInjectionExtensions.cs diff --git a/src/__Libraries/StellaOps.Plugin/DependencyInjection/StellaOpsPluginRegistration.cs b/src/StellaOps.Plugin/DependencyInjection/StellaOpsPluginRegistration.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/DependencyInjection/StellaOpsPluginRegistration.cs rename to src/StellaOps.Plugin/DependencyInjection/StellaOpsPluginRegistration.cs diff --git a/src/__Libraries/StellaOps.Plugin/Hosting/PluginAssembly.cs b/src/StellaOps.Plugin/Hosting/PluginAssembly.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Hosting/PluginAssembly.cs rename to src/StellaOps.Plugin/Hosting/PluginAssembly.cs diff --git a/src/__Libraries/StellaOps.Plugin/Hosting/PluginHost.cs b/src/StellaOps.Plugin/Hosting/PluginHost.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Hosting/PluginHost.cs rename to src/StellaOps.Plugin/Hosting/PluginHost.cs diff --git a/src/__Libraries/StellaOps.Plugin/Hosting/PluginHostOptions.cs b/src/StellaOps.Plugin/Hosting/PluginHostOptions.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Hosting/PluginHostOptions.cs rename to src/StellaOps.Plugin/Hosting/PluginHostOptions.cs diff --git a/src/__Libraries/StellaOps.Plugin/Hosting/PluginHostResult.cs b/src/StellaOps.Plugin/Hosting/PluginHostResult.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Hosting/PluginHostResult.cs rename to src/StellaOps.Plugin/Hosting/PluginHostResult.cs diff --git a/src/__Libraries/StellaOps.Plugin/Hosting/PluginLoadContext.cs b/src/StellaOps.Plugin/Hosting/PluginLoadContext.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Hosting/PluginLoadContext.cs rename to src/StellaOps.Plugin/Hosting/PluginLoadContext.cs diff --git a/src/__Libraries/StellaOps.Plugin/Internal/ReflectionExtensions.cs b/src/StellaOps.Plugin/Internal/ReflectionExtensions.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/Internal/ReflectionExtensions.cs rename to src/StellaOps.Plugin/Internal/ReflectionExtensions.cs diff --git a/src/__Libraries/StellaOps.Plugin/PluginContracts.cs b/src/StellaOps.Plugin/PluginContracts.cs similarity index 100% rename from src/__Libraries/StellaOps.Plugin/PluginContracts.cs rename to src/StellaOps.Plugin/PluginContracts.cs diff --git a/src/__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj b/src/StellaOps.Plugin/StellaOps.Plugin.csproj similarity index 100% rename from src/__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj rename to src/StellaOps.Plugin/StellaOps.Plugin.csproj