Merge all changes

This commit is contained in:
StellaOps Bot
2026-01-08 08:54:27 +02:00
parent 589de352c2
commit 110591d6bf
381 changed files with 2237 additions and 1939 deletions

View File

@@ -15,7 +15,7 @@ on:
jobs: jobs:
package-feeds: package-feeds:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
@@ -68,3 +68,4 @@ jobs:
out/advisory-ai/feeds/provenance.json out/advisory-ai/feeds/provenance.json
if-no-files-found: warn if-no-files-found: warn
retention-days: 30 retention-days: 30

View File

@@ -14,7 +14,7 @@ on:
jobs: jobs:
sealed-smoke: sealed-smoke:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
steps: steps:
@@ -26,3 +26,4 @@ jobs:
run: pip install dnslib run: pip install dnslib
- name: Run sealed-mode smoke - name: Run sealed-mode smoke
run: sudo devops/airgap/sealed-ci-smoke.sh run: sudo devops/airgap/sealed-ci-smoke.sh

View File

@@ -14,7 +14,7 @@ on:
jobs: jobs:
package-backfill: package-backfill:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
@@ -81,3 +81,4 @@ jobs:
out/aoc/SHA256SUMS out/aoc/SHA256SUMS
if-no-files-found: warn if-no-files-found: warn
retention-days: 30 retention-days: 30

View File

@@ -22,7 +22,7 @@ on:
jobs: jobs:
aoc-guard: aoc-guard:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
@@ -100,7 +100,7 @@ jobs:
aoc-verify: aoc-verify:
needs: aoc-guard needs: aoc-guard
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event_name != 'schedule' if: github.event_name != 'schedule'
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
@@ -168,3 +168,4 @@ jobs:
with: with:
name: aoc-verify-artifacts name: aoc-verify-artifacts
path: ${{ env.ARTIFACT_DIR }} path: ${{ env.ARTIFACT_DIR }}

View File

@@ -50,7 +50,7 @@ on:
jobs: jobs:
rotate: rotate:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: ${{ inputs.environment }} environment: ${{ inputs.environment }}
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -164,3 +164,4 @@ jobs:
echo "Key Path: ${{ inputs.key_path }}" echo "Key Path: ${{ inputs.key_path }}"
echo "Source: ${{ inputs.source }}" echo "Source: ${{ inputs.source }}"
echo "Algorithm: ${{ inputs.algorithm }}" echo "Algorithm: ${{ inputs.algorithm }}"

View File

@@ -55,7 +55,7 @@ env:
jobs: jobs:
profile-validation: profile-validation:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -82,7 +82,7 @@ jobs:
run: ./devops/tools/validate-profiles.sh run: ./devops/tools/validate-profiles.sh
build-test: build-test:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: ${{ github.event_name == 'pull_request' && 'preview' || 'staging' }} environment: ${{ github.event_name == 'pull_request' && 'preview' || 'staging' }}
env: env:
PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/webservice PUBLISH_DIR: ${{ github.workspace }}/artifacts/publish/webservice
@@ -590,7 +590,7 @@ PY
# Quality Gates Foundation (Sprint 0350) # Quality Gates Foundation (Sprint 0350)
# ============================================================================ # ============================================================================
quality-gates: quality-gates:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
permissions: permissions:
contents: read contents: read
@@ -674,7 +674,7 @@ PY
retention-days: 14 retention-days: 14
security-testing: security-testing:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
if: github.event_name == 'pull_request' || github.event_name == 'schedule' if: github.event_name == 'pull_request' || github.event_name == 'schedule'
permissions: permissions:
@@ -717,7 +717,7 @@ PY
retention-days: 30 retention-days: 30
mutation-testing: mutation-testing:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
if: github.event_name == 'schedule' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'mutation-test')) if: github.event_name == 'schedule' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'mutation-test'))
permissions: permissions:
@@ -790,7 +790,7 @@ PY
fi fi
sealed-mode-ci: sealed-mode-ci:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
permissions: permissions:
contents: read contents: read
@@ -828,7 +828,7 @@ PY
retention-days: 14 retention-days: 14
authority-container: authority-container:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -843,7 +843,7 @@ PY
excititor-batch-validation: excititor-batch-validation:
needs: build-test needs: build-test
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.excititor_batch == 'true') if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.excititor_batch == 'true')
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
BATCH_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results/excititor-batch BATCH_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results/excititor-batch
steps: steps:
@@ -876,7 +876,7 @@ PY
path: ${{ env.BATCH_RESULTS_DIR }} path: ${{ env.BATCH_RESULTS_DIR }}
docs: docs:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-site DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-site
steps: steps:
@@ -906,7 +906,7 @@ PY
retention-days: 7 retention-days: 7
scanner-perf: scanner-perf:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
env: env:
BENCH_DIR: src/Bench/StellaOps.Bench/Scanner.Analyzers BENCH_DIR: src/Bench/StellaOps.Bench/Scanner.Analyzers
@@ -987,7 +987,7 @@ PY
retention-days: 7 retention-days: 7
deploy: deploy:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [build-test, docs, scanner-perf] needs: [build-test, docs, scanner-perf]
if: >- if: >-
needs.build-test.result == 'success' && needs.build-test.result == 'success' &&
@@ -1160,7 +1160,7 @@ PY
echo " Ref: ${{ github.ref }}" echo " Ref: ${{ github.ref }}"
notify-smoke: notify-smoke:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: deploy needs: deploy
if: needs.deploy.result == 'success' if: needs.deploy.result == 'success'
env: env:
@@ -1202,3 +1202,4 @@ PY
- name: Run Notify smoke validation - name: Run Notify smoke validation
run: dotnet run --project tools/NotifySmokeCheck/NotifySmokeCheck.csproj --configuration Release run: dotnet run --project tools/NotifySmokeCheck/NotifySmokeCheck.csproj --configuration Release

View File

@@ -5,7 +5,7 @@ on:
jobs: jobs:
build-dataset: build-dataset:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
ARTIFACT_DIR: ${{ github.workspace }}/out/linksets ARTIFACT_DIR: ${{ github.workspace }}/out/linksets
steps: steps:
@@ -30,3 +30,4 @@ jobs:
path: | path: |
${ARTIFACT_DIR}/linksets-stage-backfill.tar.zst ${ARTIFACT_DIR}/linksets-stage-backfill.tar.zst
${ARTIFACT_DIR}/linksets-stage-backfill.tar.zst.sha256 ${ARTIFACT_DIR}/linksets-stage-backfill.tar.zst.sha256

View File

@@ -32,7 +32,7 @@ env:
jobs: jobs:
detect-drift: detect-drift:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -124,7 +124,7 @@ jobs:
create-pr: create-pr:
needs: detect-drift needs: detect-drift
if: needs.detect-drift.outputs.has_drift == 'true' && (github.event.inputs.create_pr == 'true' || github.event_name == 'schedule') if: needs.detect-drift.outputs.has_drift == 'true' && (github.event.inputs.create_pr == 'true' || github.event_name == 'schedule')
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
@@ -245,3 +245,4 @@ This commit was auto-generated by the connector-fixture-drift workflow.
issue_number: pr.number, issue_number: pr.number,
labels: ['automated', 'fixtures', 'schema-drift'] labels: ['automated', 'fixtures', 'schema-drift']
}); });

View File

@@ -17,7 +17,7 @@ on:
jobs: jobs:
crypto-audit: crypto-audit:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -42,3 +42,4 @@ jobs:
path: | path: |
scripts/audit-crypto-usage.ps1 scripts/audit-crypto-usage.ps1
retention-days: 30 retention-days: 30

View File

@@ -49,7 +49,7 @@ jobs:
detect: detect:
name: Detect Dead Paths name: Detect Dead Paths
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
has-new-dead-paths: ${{ steps.check.outputs.has_new_dead_paths }} has-new-dead-paths: ${{ steps.check.outputs.has_new_dead_paths }}
new-dead-path-count: ${{ steps.check.outputs.new_count }} new-dead-path-count: ${{ steps.check.outputs.new_count }}
@@ -354,7 +354,7 @@ jobs:
name: Post Report name: Post Report
needs: detect needs: detect
if: github.event_name == 'pull_request' && always() if: github.event_name == 'pull_request' && always()
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
@@ -436,3 +436,4 @@ jobs:
body: body body: body
}); });
} }

View File

@@ -38,7 +38,7 @@ env:
jobs: jobs:
pre-flight: pre-flight:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
identity-pattern: ${{ steps.config.outputs.identity-pattern }} identity-pattern: ${{ steps.config.outputs.identity-pattern }}
@@ -61,7 +61,7 @@ jobs:
verify-attestations: verify-attestations:
needs: pre-flight needs: pre-flight
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
@@ -123,7 +123,7 @@ jobs:
verify-provenance: verify-provenance:
needs: pre-flight needs: pre-flight
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
@@ -160,7 +160,7 @@ jobs:
create-audit-entry: create-audit-entry:
needs: [verify-attestations, verify-provenance] needs: [verify-attestations, verify-provenance]
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Install StellaOps CLI - name: Install StellaOps CLI
@@ -183,7 +183,7 @@ jobs:
approve-deployment: approve-deployment:
needs: [verify-attestations, verify-provenance, create-audit-entry] needs: [verify-attestations, verify-provenance, create-audit-entry]
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: ${{ github.event.inputs.environment }} environment: ${{ github.event.inputs.environment }}
steps: steps:
@@ -202,3 +202,4 @@ jobs:
Deployment can now proceed. Deployment can now proceed.
EOF EOF

View File

@@ -48,7 +48,7 @@ jobs:
# =========================================================================== # ===========================================================================
schema-validation: schema-validation:
name: Schema Validation name: Schema Validation
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event.inputs.skip_schema_validation != 'true' if: github.event.inputs.skip_schema_validation != 'true'
timeout-minutes: 10 timeout-minutes: 10
@@ -128,7 +128,7 @@ jobs:
needs: [schema-validation] needs: [schema-validation]
if: always() && (needs.schema-validation.result == 'success' || needs.schema-validation.result == 'skipped') if: always() && (needs.schema-validation.result == 'success' || needs.schema-validation.result == 'skipped')
name: Determinism Validation name: Determinism Validation
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
outputs: outputs:
@@ -243,7 +243,7 @@ jobs:
# =========================================================================== # ===========================================================================
update-baselines: update-baselines:
name: Update Baselines name: Update Baselines
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [schema-validation, determinism-gate] needs: [schema-validation, determinism-gate]
if: github.event_name == 'workflow_dispatch' && github.event.inputs.update_baselines == 'true' if: github.event_name == 'workflow_dispatch' && github.event.inputs.update_baselines == 'true'
@@ -293,7 +293,7 @@ jobs:
# =========================================================================== # ===========================================================================
drift-check: drift-check:
name: Drift Detection Gate name: Drift Detection Gate
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [schema-validation, determinism-gate] needs: [schema-validation, determinism-gate]
if: always() if: always()
@@ -328,3 +328,4 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "Schema Validation: ${{ needs.schema-validation.result || 'skipped' }}" >> $GITHUB_STEP_SUMMARY echo "Schema Validation: ${{ needs.schema-validation.result || 'skipped' }}" >> $GITHUB_STEP_SUMMARY
echo "Determinism Status: ${{ needs.determinism-gate.outputs.status || 'pass' }}" >> $GITHUB_STEP_SUMMARY echo "Determinism Status: ${{ needs.determinism-gate.outputs.status || 'pass' }}" >> $GITHUB_STEP_SUMMARY

View File

@@ -22,7 +22,7 @@ env:
jobs: jobs:
lint-and-preview: lint-and-preview:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-preview DOCS_OUTPUT_DIR: ${{ github.workspace }}/artifacts/docs-preview
steps: steps:
@@ -99,3 +99,4 @@ jobs:
name: feedser-docs-preview name: feedser-docs-preview
path: ${{ env.DOCS_OUTPUT_DIR }} path: ${{ env.DOCS_OUTPUT_DIR }}
retention-days: 7 retention-days: 7

View File

@@ -40,7 +40,7 @@ on:
jobs: jobs:
perf: perf:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -96,3 +96,4 @@ jobs:
path: | path: |
bench/results/epss-ingest-perf-${{ github.sha }}.json bench/results/epss-ingest-perf-${{ github.sha }}.json
retention-days: 90 retention-days: 90

View File

@@ -18,7 +18,7 @@ on:
jobs: jobs:
export-ci: export-ci:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
MINIO_ACCESS_KEY: exportci MINIO_ACCESS_KEY: exportci
@@ -83,3 +83,4 @@ jobs:
- name: Teardown MinIO - name: Teardown MinIO
if: always() if: always()
run: docker compose -f devops/export/minio-compose.yml down -v run: docker compose -f devops/export/minio-compose.yml down -v

View File

@@ -26,7 +26,7 @@ env:
jobs: jobs:
build-test: build-test:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results TEST_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results
steps: steps:
@@ -68,7 +68,7 @@ jobs:
path: ${{ env.TEST_RESULTS_DIR }} path: ${{ env.TEST_RESULTS_DIR }}
migration-validation: migration-validation:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
services: services:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
@@ -228,7 +228,7 @@ jobs:
echo "✓ Migration is idempotent" echo "✓ Migration is idempotent"
generate-manifest: generate-manifest:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [build-test, migration-validation] needs: [build-test, migration-validation]
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -323,3 +323,4 @@ jobs:
name: findings-ledger-migrations name: findings-ledger-migrations
path: out/findings-ledger/ path: out/findings-ledger/
if-no-files-found: error if-no-files-found: error

View File

@@ -18,7 +18,7 @@ on:
jobs: jobs:
refresh: refresh:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
env: env:
@@ -66,3 +66,4 @@ jobs:
path: out/feeds/icscisa-kisa/${{ steps.meta.outputs.run_date }} path: out/feeds/icscisa-kisa/${{ steps.meta.outputs.run_date }}
if-no-files-found: error if-no-files-found: error
retention-days: 21 retention-days: 21

View File

@@ -15,7 +15,7 @@ env:
jobs: jobs:
interop-tests: interop-tests:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -96,7 +96,7 @@ jobs:
fi fi
summary: summary:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: interop-tests needs: interop-tests
if: always() if: always()
@@ -126,3 +126,4 @@ jobs:
fi fi
echo "| ${format} | ${STATUS} |" >> $GITHUB_STEP_SUMMARY echo "| ${format} | ${STATUS} |" >> $GITHUB_STEP_SUMMARY
done done

View File

@@ -13,7 +13,7 @@ on:
jobs: jobs:
validate-oas: validate-oas:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -44,7 +44,7 @@ jobs:
if-no-files-found: warn if-no-files-found: warn
check-wellknown: check-wellknown:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: validate-oas needs: validate-oas
steps: steps:
- name: Checkout - name: Checkout
@@ -64,7 +64,7 @@ jobs:
fi fi
deprecation-check: deprecation-check:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: validate-oas needs: validate-oas
steps: steps:
- name: Checkout - name: Checkout
@@ -79,3 +79,4 @@ jobs:
else else
echo "[info] No deprecation policy yet (OK for initial setup)" echo "[info] No deprecation policy yet (OK for initial setup)"
fi fi

View File

@@ -18,7 +18,7 @@ on:
jobs: jobs:
build-pack: build-pack:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }}
steps: steps:
@@ -71,7 +71,7 @@ jobs:
retention-days: 30 retention-days: 30
verify-pack: verify-pack:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-pack needs: build-pack
steps: steps:
- name: Checkout - name: Checkout
@@ -99,3 +99,4 @@ jobs:
fi fi
fi fi
done done

View File

@@ -35,7 +35,7 @@ on:
jobs: jobs:
nuget-license-audit: nuget-license-audit:
name: NuGet License Audit name: NuGet License Audit
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -94,7 +94,7 @@ jobs:
npm-license-audit: npm-license-audit:
name: npm License Audit name: npm License Audit
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -142,7 +142,7 @@ jobs:
vendored-license-check: vendored-license-check:
name: Vendored Components Check name: Vendored Components Check
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -207,7 +207,7 @@ jobs:
license-compatibility-check: license-compatibility-check:
name: License Compatibility Check name: License Compatibility Check
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [nuget-license-audit, npm-license-audit] needs: [nuget-license-audit, npm-license-audit]
steps: steps:
- name: Checkout - name: Checkout
@@ -297,3 +297,4 @@ jobs:
name: license-audit-summary name: license-audit-summary
path: out/combined path: out/combined
retention-days: 90 retention-days: 90

View File

@@ -26,7 +26,7 @@ env:
jobs: jobs:
lighthouse: lighthouse:
name: Lighthouse Audit name: Lighthouse Audit
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
defaults: defaults:
run: run:
working-directory: src/Web/StellaOps.Web working-directory: src/Web/StellaOps.Web
@@ -145,7 +145,7 @@ jobs:
axe-accessibility: axe-accessibility:
name: Axe Accessibility Audit name: Axe Accessibility Audit
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
defaults: defaults:
run: run:
working-directory: src/Web/StellaOps.Web working-directory: src/Web/StellaOps.Web
@@ -186,3 +186,4 @@ jobs:
name: axe-accessibility-results name: axe-accessibility-results
path: src/Web/StellaOps.Web/test-results/ path: src/Web/StellaOps.Web/test-results/
retention-days: 30 retention-days: 30

View File

@@ -19,7 +19,7 @@ on:
jobs: jobs:
lnm-backfill: lnm-backfill:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
@@ -62,3 +62,4 @@ jobs:
with: with:
name: lnm-backfill-artifacts name: lnm-backfill-artifacts
path: ${{ env.ARTIFACT_DIR }} path: ${{ env.ARTIFACT_DIR }}

View File

@@ -15,7 +15,7 @@ on:
jobs: jobs:
build-runner: build-runner:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -60,7 +60,7 @@ jobs:
if-no-files-found: warn if-no-files-found: warn
validate-metrics: validate-metrics:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-runner needs: build-runner
steps: steps:
- name: Checkout - name: Checkout
@@ -81,3 +81,4 @@ jobs:
fi fi
echo "Monitoring config validation complete" echo "Monitoring config validation complete"

View File

@@ -23,7 +23,7 @@ on:
jobs: jobs:
vex-backfill: vex-backfill:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
@@ -61,3 +61,4 @@ jobs:
with: with:
name: lnm-vex-backfill-artifacts name: lnm-vex-backfill-artifacts
path: ${{ env.ARTIFACT_DIR }} path: ${{ env.ARTIFACT_DIR }}

View File

@@ -69,7 +69,7 @@ jobs:
discover: discover:
name: Discover Migrations name: Discover Migrations
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
modules: ${{ steps.find.outputs.modules }} modules: ${{ steps.find.outputs.modules }}
module_count: ${{ steps.find.outputs.count }} module_count: ${{ steps.find.outputs.count }}
@@ -119,7 +119,7 @@ jobs:
forward-migrations: forward-migrations:
name: Forward Migration name: Forward Migration
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
needs: discover needs: discover
if: needs.discover.outputs.module_count != '0' if: needs.discover.outputs.module_count != '0'
@@ -246,7 +246,7 @@ jobs:
rollback-migrations: rollback-migrations:
name: Rollback Migration name: Rollback Migration
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
needs: [discover, forward-migrations] needs: [discover, forward-migrations]
if: | if: |
@@ -371,7 +371,7 @@ jobs:
idempotency: idempotency:
name: Idempotency Test name: Idempotency Test
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
needs: [discover, forward-migrations] needs: [discover, forward-migrations]
if: | if: |
@@ -490,7 +490,7 @@ jobs:
summary: summary:
name: Migration Summary name: Migration Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [discover, forward-migrations, rollback-migrations, idempotency] needs: [discover, forward-migrations, rollback-migrations, idempotency]
if: always() if: always()
steps: steps:
@@ -510,3 +510,4 @@ jobs:
- name: Check for failures - name: Check for failures
if: contains(needs.*.result, 'failure') if: contains(needs.*.result, 'failure')
run: exit 1 run: exit 1

View File

@@ -7,7 +7,7 @@ on:
jobs: jobs:
mirror-sign: mirror-sign:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
MIRROR_SIGN_KEY_B64: ${{ secrets.MIRROR_SIGN_KEY_B64 }} MIRROR_SIGN_KEY_B64: ${{ secrets.MIRROR_SIGN_KEY_B64 }}
REQUIRE_PROD_SIGNING: 1 REQUIRE_PROD_SIGNING: 1
@@ -72,3 +72,4 @@ jobs:
out/mirror/thin/export-center/schedule-response.json out/mirror/thin/export-center/schedule-response.json
if-no-files-found: error if-no-files-found: error
retention-days: 14 retention-days: 14

View File

@@ -59,7 +59,7 @@ jobs:
parse-tag: parse-tag:
name: Parse Tag name: Parse Tag
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event_name == 'push' if: github.event_name == 'push'
outputs: outputs:
module: ${{ steps.parse.outputs.module }} module: ${{ steps.parse.outputs.module }}
@@ -90,7 +90,7 @@ jobs:
validate: validate:
name: Validate Inputs name: Validate Inputs
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [parse-tag] needs: [parse-tag]
if: always() && (needs.parse-tag.result == 'success' || needs.parse-tag.result == 'skipped') if: always() && (needs.parse-tag.result == 'success' || needs.parse-tag.result == 'skipped')
outputs: outputs:
@@ -139,7 +139,7 @@ jobs:
publish-nuget: publish-nuget:
name: Publish NuGet name: Publish NuGet
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
if: needs.validate.outputs.publish_nuget == 'true' if: needs.validate.outputs.publish_nuget == 'true'
steps: steps:
@@ -251,7 +251,7 @@ jobs:
publish-container: publish-container:
name: Publish Container name: Publish Container
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
if: needs.validate.outputs.publish_container == 'true' && needs.validate.outputs.module != 'CLI' if: needs.validate.outputs.publish_container == 'true' && needs.validate.outputs.module != 'CLI'
steps: steps:
@@ -310,7 +310,7 @@ jobs:
publish-cli: publish-cli:
name: Publish CLI (${{ matrix.runtime }}) name: Publish CLI (${{ matrix.runtime }})
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
if: needs.validate.outputs.module == 'CLI' if: needs.validate.outputs.module == 'CLI'
strategy: strategy:
@@ -378,7 +378,7 @@ jobs:
summary: summary:
name: Publish Summary name: Publish Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, publish-nuget, publish-container, publish-cli] needs: [validate, publish-nuget, publish-container, publish-cli]
if: always() if: always()
steps: steps:
@@ -403,3 +403,4 @@ jobs:
run: | run: |
echo "::error::One or more publish jobs failed" echo "::error::One or more publish jobs failed"
exit 1 exit 1

View File

@@ -47,7 +47,7 @@ jobs:
prepare: prepare:
name: Prepare Nightly Run name: Prepare Nightly Run
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
run_id: ${{ steps.metadata.outputs.run_id }} run_id: ${{ steps.metadata.outputs.run_id }}
run_date: ${{ steps.metadata.outputs.run_date }} run_date: ${{ steps.metadata.outputs.run_date }}
@@ -88,7 +88,7 @@ jobs:
build: build:
name: Full Build name: Full Build
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
needs: prepare needs: prepare
steps: steps:
@@ -126,7 +126,7 @@ jobs:
test-pr-gating: test-pr-gating:
name: PR-Gating Tests name: PR-Gating Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 45 timeout-minutes: 45
needs: build needs: build
services: services:
@@ -184,7 +184,7 @@ jobs:
test-extended: test-extended:
name: Extended Tests name: Extended Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 60 timeout-minutes: 60
needs: build needs: build
if: github.event.inputs.skip_performance != 'true' if: github.event.inputs.skip_performance != 'true'
@@ -227,7 +227,7 @@ jobs:
determinism: determinism:
name: Determinism Verification name: Determinism Verification
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 45 timeout-minutes: 45
needs: build needs: build
if: github.event.inputs.skip_determinism != 'true' if: github.event.inputs.skip_determinism != 'true'
@@ -289,7 +289,7 @@ jobs:
cross-module: cross-module:
name: Cross-Module Validation name: Cross-Module Validation
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
needs: build needs: build
steps: steps:
@@ -341,7 +341,7 @@ jobs:
coverage: coverage:
name: Code Coverage name: Code Coverage
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 45 timeout-minutes: 45
needs: build needs: build
services: services:
@@ -415,7 +415,7 @@ jobs:
summary: summary:
name: Nightly Summary name: Nightly Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: needs:
- prepare - prepare
- build - build
@@ -483,3 +483,4 @@ jobs:
- name: Exit with appropriate code - name: Exit with appropriate code
if: steps.status.outputs.status == 'failure' if: steps.status.outputs.status == 'failure'
run: exit 1 run: exit 1

View File

@@ -16,7 +16,7 @@ env:
jobs: jobs:
offline-e2e: offline-e2e:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
@@ -82,7 +82,7 @@ jobs:
path: ./results/ path: ./results/
verify-isolation: verify-isolation:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: offline-e2e needs: offline-e2e
if: always() if: always()
@@ -119,3 +119,4 @@ jobs:
else else
echo "⚠️ No test results found" >> $GITHUB_STEP_SUMMARY echo "⚠️ No test results found" >> $GITHUB_STEP_SUMMARY
fi fi

View File

@@ -17,7 +17,7 @@ on:
jobs: jobs:
policy-lint: policy-lint:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -68,3 +68,4 @@ jobs:
name: policy-lint name: policy-lint
path: out/policy-lint path: out/policy-lint
retention-days: 7 retention-days: 7

View File

@@ -17,7 +17,7 @@ on:
jobs: jobs:
policy-simulate: policy-simulate:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -87,3 +87,4 @@ jobs:
name: policy-signing name: policy-signing
path: out/policy-sign path: out/policy-sign
retention-days: 7 retention-days: 7

View File

@@ -19,7 +19,7 @@ on:
jobs: jobs:
promote: promote:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: production environment: production
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -207,3 +207,4 @@ jobs:
else else
echo " Docs: skipped" echo " Docs: skipped"
fi fi

View File

@@ -34,7 +34,7 @@ on:
jobs: jobs:
benchmark: benchmark:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -242,7 +242,7 @@ jobs:
update-baseline: update-baseline:
needs: benchmark needs: benchmark
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.benchmark.outputs.regression != 'true' if: github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.benchmark.outputs.regression != 'true'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -263,7 +263,7 @@ jobs:
notify-pr: notify-pr:
needs: benchmark needs: benchmark
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
@@ -304,3 +304,4 @@ jobs:
repo: context.repo.repo, repo: context.repo.repo,
body: body body: body
}); });

View File

@@ -20,7 +20,7 @@ on:
jobs: jobs:
validate-corpus: validate-corpus:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -136,7 +136,7 @@ jobs:
retention-days: 14 retention-days: 14
validate-ground-truths: validate-ground-truths:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
TZ: UTC TZ: UTC
steps: steps:
@@ -205,7 +205,7 @@ jobs:
EOF EOF
determinism-check: determinism-check:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
TZ: UTC TZ: UTC
needs: validate-corpus needs: validate-corpus
@@ -265,3 +265,4 @@ jobs:
print(f"Checked {len(json_files)} JSON files") print(f"Checked {len(json_files)} JSON files")
EOF EOF

View File

@@ -37,7 +37,7 @@ env:
jobs: jobs:
sign-images: sign-images:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
id-token: write id-token: write
contents: read contents: read
@@ -170,7 +170,7 @@ jobs:
--registry "stellaops/gateway" --registry "stellaops/gateway"
sign-binaries: sign-binaries:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
id-token: write id-token: write
contents: read contents: read
@@ -329,7 +329,7 @@ jobs:
verify-signatures: verify-signatures:
needs: [sign-images, sign-binaries] needs: [sign-images, sign-binaries]
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
packages: read packages: read
@@ -397,3 +397,4 @@ jobs:
--certificate-oidc-issuer "https://git.stella-ops.org" --certificate-oidc-issuer "https://git.stella-ops.org"
\`\`\` \`\`\`
EOF EOF

View File

@@ -50,7 +50,7 @@ jobs:
parse-tag: parse-tag:
name: Parse Tag name: Parse Tag
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event_name == 'push' if: github.event_name == 'push'
outputs: outputs:
version: ${{ steps.parse.outputs.version }} version: ${{ steps.parse.outputs.version }}
@@ -89,7 +89,7 @@ jobs:
validate: validate:
name: Validate Release name: Validate Release
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [parse-tag] needs: [parse-tag]
if: always() && (needs.parse-tag.result == 'success' || needs.parse-tag.result == 'skipped') if: always() && (needs.parse-tag.result == 'success' || needs.parse-tag.result == 'skipped')
outputs: outputs:
@@ -150,7 +150,7 @@ jobs:
test-gate: test-gate:
name: Test Gate name: Test Gate
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
if: github.event.inputs.skip_tests != 'true' if: github.event.inputs.skip_tests != 'true'
steps: steps:
@@ -192,7 +192,7 @@ jobs:
build-modules: build-modules:
name: Build ${{ matrix.module }} name: Build ${{ matrix.module }}
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, test-gate] needs: [validate, test-gate]
if: always() && needs.validate.result == 'success' && (needs.test-gate.result == 'success' || needs.test-gate.result == 'skipped') if: always() && needs.validate.result == 'success' && (needs.test-gate.result == 'success' || needs.test-gate.result == 'skipped')
strategy: strategy:
@@ -293,7 +293,7 @@ jobs:
build-containers: build-containers:
name: Container ${{ matrix.module }} name: Container ${{ matrix.module }}
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-modules] needs: [validate, build-modules]
if: needs.validate.outputs.dry_run != 'true' if: needs.validate.outputs.dry_run != 'true'
strategy: strategy:
@@ -351,7 +351,7 @@ jobs:
build-cli: build-cli:
name: CLI (${{ matrix.runtime }}) name: CLI (${{ matrix.runtime }})
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, test-gate] needs: [validate, test-gate]
if: always() && needs.validate.result == 'success' && (needs.test-gate.result == 'success' || needs.test-gate.result == 'skipped') if: always() && needs.validate.result == 'success' && (needs.test-gate.result == 'success' || needs.test-gate.result == 'skipped')
strategy: strategy:
@@ -421,7 +421,7 @@ jobs:
build-helm: build-helm:
name: Helm Chart name: Helm Chart
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
steps: steps:
- name: Checkout - name: Checkout
@@ -459,7 +459,7 @@ jobs:
release-manifest: release-manifest:
name: Release Manifest name: Release Manifest
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-modules, build-cli, build-helm] needs: [validate, build-modules, build-cli, build-helm]
if: always() && needs.validate.result == 'success' if: always() && needs.validate.result == 'success'
steps: steps:
@@ -538,7 +538,7 @@ jobs:
generate-changelog: generate-changelog:
name: Generate Changelog name: Generate Changelog
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-modules] needs: [validate, build-modules]
if: always() && needs.validate.result == 'success' if: always() && needs.validate.result == 'success'
steps: steps:
@@ -595,7 +595,7 @@ jobs:
generate-suite-docs: generate-suite-docs:
name: Generate Suite Docs name: Generate Suite Docs
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, generate-changelog, release-manifest] needs: [validate, generate-changelog, release-manifest]
if: always() && needs.validate.result == 'success' if: always() && needs.validate.result == 'success'
steps: steps:
@@ -658,7 +658,7 @@ jobs:
generate-compose: generate-compose:
name: Generate Docker Compose name: Generate Docker Compose
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, release-manifest] needs: [validate, release-manifest]
if: always() && needs.validate.result == 'success' if: always() && needs.validate.result == 'success'
steps: steps:
@@ -704,7 +704,7 @@ jobs:
commit-docs: commit-docs:
name: Commit Documentation name: Commit Documentation
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, generate-suite-docs, generate-compose, create-release] needs: [validate, generate-suite-docs, generate-compose, create-release]
if: needs.validate.outputs.dry_run != 'true' && needs.create-release.result == 'success' if: needs.validate.outputs.dry_run != 'true' && needs.create-release.result == 'success'
steps: steps:
@@ -765,7 +765,7 @@ jobs:
create-release: create-release:
name: Create Gitea Release name: Create Gitea Release
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest] needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest]
if: needs.validate.outputs.dry_run != 'true' if: needs.validate.outputs.dry_run != 'true'
steps: steps:
@@ -877,7 +877,7 @@ jobs:
summary: summary:
name: Release Summary name: Release Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest, generate-changelog, generate-suite-docs, generate-compose, create-release, commit-docs] needs: [validate, build-modules, build-containers, build-cli, build-helm, release-manifest, generate-changelog, generate-suite-docs, generate-compose, create-release, commit-docs]
if: always() if: always()
steps: steps:
@@ -912,3 +912,4 @@ jobs:
run: | run: |
echo "::error::One or more release jobs failed" echo "::error::One or more release jobs failed"
exit 1 exit 1

View File

@@ -34,7 +34,7 @@ on:
jobs: jobs:
build-release: build-release:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
REGISTRY: registry.stella-ops.org REGISTRY: registry.stella-ops.org
@@ -249,3 +249,4 @@ jobs:
name: stellaops-debug-${{ steps.meta.outputs.version }} name: stellaops-debug-${{ steps.meta.outputs.version }}
path: out/release/debug path: out/release/debug
if-no-files-found: error if-no-files-found: error

View File

@@ -1,4 +1,4 @@
name: Replay Verification name: Replay Verification
on: on:
pull_request: pull_request:
@@ -11,7 +11,7 @@ on:
jobs: jobs:
replay-verification: replay-verification:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -37,3 +37,4 @@ jobs:
with: with:
name: replay-diff-report name: replay-diff-report
path: results/diff-report.json path: results/diff-report.json

View File

@@ -30,7 +30,7 @@ on:
jobs: jobs:
risk-bundle-build: risk-bundle-build:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_VERSION: '10.0.100' DOTNET_VERSION: '10.0.100'
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
@@ -101,7 +101,7 @@ jobs:
path: ${{ env.ARTIFACT_DIR }}/*.trx path: ${{ env.ARTIFACT_DIR }}/*.trx
risk-bundle-offline-kit: risk-bundle-offline-kit:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: risk-bundle-build needs: risk-bundle-build
env: env:
ARTIFACT_DIR: ${{ github.workspace }}/.artifacts ARTIFACT_DIR: ${{ github.workspace }}/.artifacts
@@ -156,7 +156,7 @@ jobs:
path: ${{ env.OFFLINE_KIT_DIR }} path: ${{ env.OFFLINE_KIT_DIR }}
publish-checksums: publish-checksums:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: risk-bundle-build needs: risk-bundle-build
if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event.inputs.publish_checksums == 'true') if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event.inputs.publish_checksums == 'true')
env: env:
@@ -196,3 +196,4 @@ jobs:
with: with:
name: risk-bundle-published-checksums name: risk-bundle-published-checksums
path: out/checksums/risk-bundle/ path: out/checksums/risk-bundle/

View File

@@ -60,7 +60,7 @@ jobs:
preflight: preflight:
name: Pre-Flight Checks name: Pre-Flight Checks
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: ${{ inputs.environment || 'staging' }} environment: ${{ inputs.environment || 'staging' }}
outputs: outputs:
current-version: ${{ steps.current.outputs.version }} current-version: ${{ steps.current.outputs.version }}
@@ -173,7 +173,7 @@ jobs:
name: Measure Rollback Lag name: Measure Rollback Lag
needs: preflight needs: preflight
if: needs.preflight.outputs.can-rollback == 'true' if: needs.preflight.outputs.can-rollback == 'true'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
environment: ${{ inputs.environment || 'staging' }} environment: ${{ inputs.environment || 'staging' }}
outputs: outputs:
rollback-time: ${{ steps.timing.outputs.rollback_time }} rollback-time: ${{ steps.timing.outputs.rollback_time }}
@@ -328,7 +328,7 @@ jobs:
name: Generate Report name: Generate Report
needs: [preflight, measure] needs: [preflight, measure]
if: always() && needs.preflight.result == 'success' if: always() && needs.preflight.result == 'success'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Generate Report - name: Generate Report
run: | run: |
@@ -401,3 +401,4 @@ jobs:
SLO_SECONDS="${{ inputs.rollback_slo_seconds || 300 }}" SLO_SECONDS="${{ inputs.rollback_slo_seconds || 300 }}"
echo "::error::Rollback took ${TOTAL_LAG}s, exceeds SLO of ${SLO_SECONDS}s" echo "::error::Rollback took ${TOTAL_LAG}s, exceeds SLO of ${SLO_SECONDS}s"
exit 1 exit 1

View File

@@ -33,7 +33,7 @@ env:
jobs: jobs:
load-tests: load-tests:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
services: services:
@@ -139,7 +139,7 @@ jobs:
fi fi
chaos-unit-tests: chaos-unit-tests:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
needs: load-tests needs: load-tests
if: always() if: always()
@@ -198,7 +198,7 @@ jobs:
retention-days: 30 retention-days: 30
valkey-failure-tests: valkey-failure-tests:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
needs: load-tests needs: load-tests
if: ${{ github.event.inputs.run_valkey_tests != 'false' }} if: ${{ github.event.inputs.run_valkey_tests != 'false' }}
@@ -235,7 +235,7 @@ jobs:
path: results/ path: results/
analyze-results: analyze-results:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [load-tests, chaos-unit-tests] needs: [load-tests, chaos-unit-tests]
if: always() if: always()
@@ -304,3 +304,4 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "### Thresholds" >> $GITHUB_STEP_SUMMARY echo "### Thresholds" >> $GITHUB_STEP_SUMMARY
echo "- Status: ${{ steps.analysis.outputs.thresholds_passed == 'true' && 'PASSED' || 'FAILED' }}" >> $GITHUB_STEP_SUMMARY echo "- Status: ${{ steps.analysis.outputs.thresholds_passed == 'true' && 'PASSED' || 'FAILED' }}" >> $GITHUB_STEP_SUMMARY

View File

@@ -74,7 +74,7 @@ jobs:
sast-scan: sast-scan:
name: SAST Analysis name: SAST Analysis
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
permissions: permissions:
security-events: write security-events: write
@@ -197,7 +197,7 @@ jobs:
dotnet-security: dotnet-security:
name: .NET Security Analysis name: .NET Security Analysis
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- name: Checkout - name: Checkout
@@ -257,7 +257,7 @@ jobs:
dependency-check: dependency-check:
name: Dependency Vulnerabilities name: Dependency Vulnerabilities
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout - name: Checkout
@@ -308,7 +308,7 @@ jobs:
dockerfile-lint: dockerfile-lint:
name: Dockerfile Security name: Dockerfile Security
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout - name: Checkout
@@ -362,7 +362,7 @@ jobs:
summary: summary:
name: SAST Summary name: SAST Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [sast-scan, dotnet-security, dependency-check, dockerfile-lint] needs: [sast-scan, dotnet-security, dependency-check, dockerfile-lint]
if: always() if: always()
steps: steps:
@@ -384,3 +384,4 @@ jobs:
needs.dotnet-security.result == 'failure' || needs.dotnet-security.result == 'failure' ||
needs.dependency-check.result == 'failure') needs.dependency-check.result == 'failure')
run: exit 1 run: exit 1

View File

@@ -53,7 +53,7 @@ jobs:
discover: discover:
name: Discover Changed Modules name: Discover Changed Modules
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
modules: ${{ steps.detect.outputs.modules }} modules: ${{ steps.detect.outputs.modules }}
has-schema-changes: ${{ steps.detect.outputs.has_changes }} has-schema-changes: ${{ steps.detect.outputs.has_changes }}
@@ -126,7 +126,7 @@ jobs:
name: Test ${{ matrix.module }} (Schema ${{ matrix.schema-version }}) name: Test ${{ matrix.module }} (Schema ${{ matrix.schema-version }})
needs: discover needs: discover
if: needs.discover.outputs.has-schema-changes == 'true' || github.event_name == 'workflow_dispatch' if: needs.discover.outputs.has-schema-changes == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -275,7 +275,7 @@ jobs:
name: Generate Compatibility Report name: Generate Compatibility Report
needs: [discover, test] needs: [discover, test]
if: always() && needs.discover.outputs.has-schema-changes == 'true' if: always() && needs.discover.outputs.has-schema-changes == 'true'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Download All Results - name: Download All Results
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -337,7 +337,7 @@ jobs:
name: Post Report to PR name: Post Report to PR
needs: [discover, test, report] needs: [discover, test, report]
if: github.event_name == 'pull_request' && always() if: github.event_name == 'pull_request' && always()
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
@@ -416,3 +416,4 @@ jobs:
body: body body: body
}); });
} }

View File

@@ -17,7 +17,7 @@ on:
jobs: jobs:
sdk-publish: sdk-publish:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -89,3 +89,4 @@ jobs:
.nuget/packages/*.nupkg .nuget/packages/*.nupkg
if-no-files-found: warn if-no-files-found: warn
retention-days: 7 retention-days: 7

View File

@@ -47,7 +47,7 @@ jobs:
validate: validate:
name: Validate Inputs name: Validate Inputs
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
version: ${{ steps.resolve.outputs.version }} version: ${{ steps.resolve.outputs.version }}
sign_bundle: ${{ steps.resolve.outputs.sign_bundle }} sign_bundle: ${{ steps.resolve.outputs.sign_bundle }}
@@ -95,7 +95,7 @@ jobs:
build-bundle: build-bundle:
name: Build Secrets Bundle name: Build Secrets Bundle
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate] needs: [validate]
steps: steps:
- name: Checkout - name: Checkout
@@ -201,7 +201,7 @@ jobs:
sign-bundle: sign-bundle:
name: Sign Secrets Bundle name: Sign Secrets Bundle
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-bundle] needs: [validate, build-bundle]
if: needs.validate.outputs.sign_bundle == 'true' if: needs.validate.outputs.sign_bundle == 'true'
steps: steps:
@@ -305,7 +305,7 @@ jobs:
package-offline-kit: package-offline-kit:
name: Package for Offline Kit name: Package for Offline Kit
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-bundle, sign-bundle] needs: [validate, build-bundle, sign-bundle]
if: always() && needs.build-bundle.result == 'success' && needs.validate.outputs.include_in_kit == 'true' if: always() && needs.build-bundle.result == 'success' && needs.validate.outputs.include_in_kit == 'true'
steps: steps:
@@ -357,7 +357,7 @@ jobs:
publish: publish:
name: Publish Bundle name: Publish Bundle
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, sign-bundle, package-offline-kit] needs: [validate, sign-bundle, package-offline-kit]
if: needs.validate.outputs.dry_run != 'true' && needs.sign-bundle.result == 'success' if: needs.validate.outputs.dry_run != 'true' && needs.sign-bundle.result == 'success'
steps: steps:
@@ -478,7 +478,7 @@ jobs:
summary: summary:
name: Build Summary name: Build Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [validate, build-bundle, sign-bundle, package-offline-kit, publish] needs: [validate, build-bundle, sign-bundle, package-offline-kit, publish]
if: always() if: always()
steps: steps:
@@ -501,3 +501,4 @@ jobs:
echo "| Sign Bundle | ${{ needs.sign-bundle.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Sign Bundle | ${{ needs.sign-bundle.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Package Offline Kit | ${{ needs.package-offline-kit.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Package Offline Kit | ${{ needs.package-offline-kit.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Publish | ${{ needs.publish.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Publish | ${{ needs.publish.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY

View File

@@ -19,7 +19,7 @@ on:
jobs: jobs:
signals-ci: signals-ci:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -73,3 +73,4 @@ jobs:
out/signals out/signals
out/signals/signals-image.tar out/signals/signals-image.tar
retention-days: 7 retention-days: 7

View File

@@ -22,7 +22,7 @@ on:
jobs: jobs:
sign-signals-artifacts: sign-signals-artifacts:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
@@ -120,7 +120,7 @@ jobs:
echo "Artifacts available as workflow artifact for manual ingestion" echo "Artifacts available as workflow artifact for manual ingestion"
verify-signatures: verify-signatures:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: sign-signals-artifacts needs: sign-signals-artifacts
steps: steps:
- name: Checkout - name: Checkout
@@ -181,3 +181,4 @@ jobs:
done done
echo "" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Run ID: ${{ github.run_number }}" >> "$GITHUB_STEP_SUMMARY" echo "Run ID: ${{ github.run_number }}" >> "$GITHUB_STEP_SUMMARY"

View File

@@ -21,7 +21,7 @@ on:
jobs: jobs:
reachability-smoke: reachability-smoke:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
DOTNET_NOLOGO: 1 DOTNET_NOLOGO: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -54,7 +54,7 @@ jobs:
scripts/signals/reachability-smoke.sh scripts/signals/reachability-smoke.sh
sign-and-upload: sign-and-upload:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: reachability-smoke needs: reachability-smoke
env: env:
COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }} COSIGN_PRIVATE_KEY_B64: ${{ secrets.COSIGN_PRIVATE_KEY_B64 }}
@@ -125,3 +125,4 @@ jobs:
if: ${{ env.CI_EVIDENCE_LOCKER_TOKEN == '' || env.EVIDENCE_LOCKER_URL == '' }} if: ${{ env.CI_EVIDENCE_LOCKER_TOKEN == '' || env.EVIDENCE_LOCKER_URL == '' }}
run: | run: |
echo "::notice::Evidence Locker upload skipped (CI_EVIDENCE_LOCKER_TOKEN or EVIDENCE_LOCKER_URL not set)" echo "::notice::Evidence Locker upload skipped (CI_EVIDENCE_LOCKER_TOKEN or EVIDENCE_LOCKER_URL not set)"

View File

@@ -17,7 +17,7 @@ on:
jobs: jobs:
symbols-smoke: symbols-smoke:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
ARTIFACT_DIR: ${{ github.workspace }}/artifacts/symbols-ci ARTIFACT_DIR: ${{ github.workspace }}/artifacts/symbols-ci
steps: steps:
@@ -45,3 +45,4 @@ jobs:
name: symbols-ci name: symbols-ci
path: ${{ env.ARTIFACT_DIR }} path: ${{ env.ARTIFACT_DIR }}
retention-days: 7 retention-days: 7

View File

@@ -8,7 +8,7 @@ on:
jobs: jobs:
symbols-release-smoke: symbols-release-smoke:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
env: env:
ARTIFACT_DIR: ${{ github.workspace }}/artifacts/symbols-release ARTIFACT_DIR: ${{ github.workspace }}/artifacts/symbols-release
steps: steps:
@@ -39,3 +39,4 @@ jobs:
name: symbols-release name: symbols-release
path: ${{ env.ARTIFACT_DIR }} path: ${{ env.ARTIFACT_DIR }}
retention-days: 14 retention-days: 14

View File

@@ -39,7 +39,7 @@ jobs:
validate: validate:
name: Validate Annotations name: Validate Annotations
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
has-violations: ${{ steps.validate.outputs.has_violations }} has-violations: ${{ steps.validate.outputs.has_violations }}
violation-count: ${{ steps.validate.outputs.violation_count }} violation-count: ${{ steps.validate.outputs.violation_count }}
@@ -205,7 +205,7 @@ jobs:
name: Post Report name: Post Report
needs: validate needs: validate
if: github.event_name == 'pull_request' && needs.validate.outputs.has-violations == 'true' if: github.event_name == 'pull_request' && needs.validate.outputs.has-violations == 'true'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
@@ -253,3 +253,4 @@ jobs:
issue_number: context.issue.number, issue_number: context.issue.number,
body: body body: body
}); });

View File

@@ -45,7 +45,7 @@ jobs:
detect-changes: detect-changes:
name: Detect Changes name: Detect Changes
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
has-test-changes: ${{ steps.changes.outputs.tests }} has-test-changes: ${{ steps.changes.outputs.tests }}
has-schema-changes: ${{ steps.changes.outputs.schema }} has-schema-changes: ${{ steps.changes.outputs.schema }}
@@ -109,7 +109,7 @@ jobs:
name: Blast-Radius Validation name: Blast-Radius Validation
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.has-test-changes == 'true' || inputs.run_all == true || github.event_name == 'schedule' if: needs.detect-changes.outputs.has-test-changes == 'true' || inputs.run_all == true || github.event_name == 'schedule'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
status: ${{ steps.validate.outputs.status }} status: ${{ steps.validate.outputs.status }}
violations: ${{ steps.validate.outputs.violation_count }} violations: ${{ steps.validate.outputs.violation_count }}
@@ -156,7 +156,7 @@ jobs:
name: Dead-Path Detection name: Dead-Path Detection
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.has-code-changes == 'true' || inputs.run_all == true || github.event_name == 'schedule' if: needs.detect-changes.outputs.has-code-changes == 'true' || inputs.run_all == true || github.event_name == 'schedule'
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
status: ${{ steps.detect.outputs.status }} status: ${{ steps.detect.outputs.status }}
new-paths: ${{ steps.detect.outputs.new_paths }} new-paths: ${{ steps.detect.outputs.new_paths }}
@@ -213,7 +213,7 @@ jobs:
name: Schema Evolution Check name: Schema Evolution Check
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.has-schema-changes == 'true' || inputs.run_all == true if: needs.detect-changes.outputs.has-schema-changes == 'true' || inputs.run_all == true
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
services: services:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
@@ -274,7 +274,7 @@ jobs:
name: Config-Diff Check name: Config-Diff Check
needs: detect-changes needs: detect-changes
if: needs.detect-changes.outputs.has-config-changes == 'true' || inputs.run_all == true if: needs.detect-changes.outputs.has-config-changes == 'true' || inputs.run_all == true
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
status: ${{ steps.test.outputs.status }} status: ${{ steps.test.outputs.status }}
tested-configs: ${{ steps.test.outputs.tested }} tested-configs: ${{ steps.test.outputs.tested }}
@@ -319,7 +319,7 @@ jobs:
name: Generate Report name: Generate Report
needs: [detect-changes, blast-radius, dead-paths, schema-evolution, config-diff] needs: [detect-changes, blast-radius, dead-paths, schema-evolution, config-diff]
if: always() if: always()
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
steps: steps:
- name: Generate Infrastructure Report - name: Generate Infrastructure Report
run: | run: |
@@ -417,7 +417,7 @@ jobs:
name: Post PR Comment name: Post PR Comment
needs: [report, blast-radius, dead-paths, schema-evolution, config-diff] needs: [report, blast-radius, dead-paths, schema-evolution, config-diff]
if: github.event_name == 'pull_request' && always() if: github.event_name == 'pull_request' && always()
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
pull-requests: write pull-requests: write
steps: steps:
@@ -504,3 +504,4 @@ jobs:
body: body body: body
}); });
} }

View File

@@ -38,7 +38,7 @@ jobs:
# =========================================================================== # ===========================================================================
unit-tests: unit-tests:
name: Unit Tests name: Unit Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -79,7 +79,7 @@ jobs:
# =========================================================================== # ===========================================================================
architecture-tests: architecture-tests:
name: Architecture Tests name: Architecture Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -121,7 +121,7 @@ jobs:
# =========================================================================== # ===========================================================================
contract-tests: contract-tests:
name: Contract Tests name: Contract Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -162,7 +162,7 @@ jobs:
# =========================================================================== # ===========================================================================
integration-tests: integration-tests:
name: Integration Tests name: Integration Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -205,7 +205,7 @@ jobs:
# =========================================================================== # ===========================================================================
security-tests: security-tests:
name: Security Tests name: Security Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
- name: Checkout repository - name: Checkout repository
@@ -246,7 +246,7 @@ jobs:
# =========================================================================== # ===========================================================================
performance-tests: performance-tests:
name: Performance Tests name: Performance Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_performance == 'true') if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_performance == 'true')
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
@@ -288,7 +288,7 @@ jobs:
# =========================================================================== # ===========================================================================
live-tests: live-tests:
name: Live Tests (External Dependencies) name: Live Tests (External Dependencies)
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
if: github.event_name == 'workflow_dispatch' && github.event.inputs.run_live == 'true' if: github.event_name == 'workflow_dispatch' && github.event.inputs.run_live == 'true'
timeout-minutes: 20 timeout-minutes: 20
steps: steps:
@@ -331,7 +331,7 @@ jobs:
# =========================================================================== # ===========================================================================
test-summary: test-summary:
name: Test Results Summary name: Test Results Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [unit-tests, architecture-tests, contract-tests, integration-tests, security-tests] needs: [unit-tests, architecture-tests, contract-tests, integration-tests, security-tests]
if: always() if: always()
steps: steps:
@@ -356,3 +356,4 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "See individual job logs for detailed test output." >> $GITHUB_STEP_SUMMARY echo "See individual job logs for detailed test output." >> $GITHUB_STEP_SUMMARY

View File

@@ -73,7 +73,7 @@ jobs:
discover: discover:
name: Discover Tests name: Discover Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
outputs: outputs:
test-projects: ${{ steps.find.outputs.projects }} test-projects: ${{ steps.find.outputs.projects }}
test-count: ${{ steps.find.outputs.count }} test-count: ${{ steps.find.outputs.count }}
@@ -120,7 +120,7 @@ jobs:
pr-gating-tests: pr-gating-tests:
name: ${{ matrix.category }} Tests name: ${{ matrix.category }} Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: ${{ matrix.timeout }} timeout-minutes: ${{ matrix.timeout }}
needs: discover needs: discover
strategy: strategy:
@@ -178,7 +178,7 @@ jobs:
integration: integration:
name: Integration Tests name: Integration Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: 45 timeout-minutes: 45
needs: discover needs: discover
services: services:
@@ -229,7 +229,7 @@ jobs:
extended-tests: extended-tests:
name: ${{ matrix.category }} Tests name: ${{ matrix.category }} Tests
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
timeout-minutes: ${{ matrix.timeout }} timeout-minutes: ${{ matrix.timeout }}
needs: discover needs: discover
if: >- if: >-
@@ -321,7 +321,7 @@ jobs:
summary: summary:
name: Test Summary name: Test Summary
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: [discover, pr-gating-tests, integration] needs: [discover, pr-gating-tests, integration]
if: always() if: always()
steps: steps:
@@ -372,3 +372,4 @@ jobs:
- name: Check for failures - name: Check for failures
if: contains(needs.*.result, 'failure') if: contains(needs.*.result, 'failure')
run: exit 1 run: exit 1

View File

@@ -29,7 +29,7 @@ env:
jobs: jobs:
scan-and-check-budget: scan-and-check-budget:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
@@ -197,3 +197,4 @@ jobs:
if: steps.env.outputs.environment != 'prod' && steps.budget.outputs.exit_code == '2' if: steps.env.outputs.environment != 'prod' && steps.budget.outputs.exit_code == '2'
run: | run: |
echo "::warning::Unknowns budget exceeded for ${{ steps.env.outputs.environment }}" echo "::warning::Unknowns budget exceeded for ${{ steps.env.outputs.environment }}"

View File

@@ -48,15 +48,16 @@
| 14 | CICD-VAL-021 | TODO | CICD-VAL-020 | DevOps | Run test-matrix.yml with act (PR trigger). | | 14 | CICD-VAL-021 | TODO | CICD-VAL-020 | DevOps | Run test-matrix.yml with act (PR trigger). |
| 15 | CICD-VAL-022 | TODO | CICD-VAL-021 | DevOps | Run build-test-deploy.yml with act (PR trigger). | | 15 | CICD-VAL-022 | TODO | CICD-VAL-021 | DevOps | Run build-test-deploy.yml with act (PR trigger). |
| 16 | CICD-VAL-023 | TODO | CICD-VAL-022 | DevOps | Document act simulation results and gaps. | | 16 | CICD-VAL-023 | TODO | CICD-VAL-022 | DevOps | Document act simulation results and gaps. |
| 17 | CICD-VAL-024 | DONE | Runner labels available | DevOps | Align workflow runner labels with available Gitea runner pool (configurable Linux label). |
### Phase 4: Test Failure Remediation ### Phase 4: Test Failure Remediation
| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | # | Task ID | Status | Key dependency / next step | Owners | Task Definition |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 17 | CICD-VAL-030 | TODO | CICD-VAL-016 | DevOps | Fix test categorization (move integration tests from Unit). | | 18 | CICD-VAL-030 | TODO | CICD-VAL-016 | DevOps | Fix test categorization (move integration tests from Unit). |
| 18 | CICD-VAL-031 | TODO | CICD-VAL-030 | DevOps | Fix PostgreSQL connection/fixture issues in tests. | | 19 | CICD-VAL-031 | TODO | CICD-VAL-030 | DevOps | Fix PostgreSQL connection/fixture issues in tests. |
| 19 | CICD-VAL-032 | TODO | CICD-VAL-031 | DevOps | Fix golden fixture mismatches. | | 20 | CICD-VAL-032 | TODO | CICD-VAL-031 | DevOps | Fix golden fixture mismatches. |
| 20 | CICD-VAL-033 | TODO | CICD-VAL-032 | DevOps | Fix remaining test failures (actual bugs). | | 21 | CICD-VAL-033 | TODO | CICD-VAL-032 | DevOps | Fix remaining test failures (actual bugs). |
| 21 | CICD-VAL-034 | TODO | CICD-VAL-033 | DevOps | Re-run full test suite to verify all green. | | 22 | CICD-VAL-034 | TODO | CICD-VAL-033 | DevOps | Re-run full test suite to verify all green. |
## Execution Log ## Execution Log
| Date (UTC) | Update | Owner | | Date (UTC) | Update | Owner |
@@ -72,6 +73,10 @@
| 2026-01-06 | BLOCKED: Act workflow simulation not yet run. Requires CI image build and further investigation of test categorization issues. | DevOps | | 2026-01-06 | BLOCKED: Act workflow simulation not yet run. Requires CI image build and further investigation of test categorization issues. | DevOps |
| 2026-01-06 | SCOPE EXPANDED: Sprint amended to include Phase 2 (local test execution), Phase 3 (act simulation), Phase 4 (test remediation). | Planning | | 2026-01-06 | SCOPE EXPANDED: Sprint amended to include Phase 2 (local test execution), Phase 3 (act simulation), Phase 4 (test remediation). | Planning |
| 2026-01-06 | CICD-VAL-010 analysis: 137 passed, 85 failed, 10 aborted. Root cause: 133 PostgreSQL connection exhaustion errors. Many tests tagged "Unit" require DB. | DevOps | | 2026-01-06 | CICD-VAL-010 analysis: 137 passed, 85 failed, 10 aborted. Root cause: 133 PostgreSQL connection exhaustion errors. Many tests tagged "Unit" require DB. | DevOps |
| 2026-01-07 | Updated workflows to use a configurable Linux runner label defaulting to ubuntu-latest to avoid runner label mismatches. | DevOps |
| 2026-01-07 | Act dry-run for test-matrix (pr-gating-tests job only) progresses through discover and matrix setup; integration job still pending due to act service container handling. | DevOps |
| 2026-01-07 | Local smoke build step exceeded 10 minutes and was stopped; unit-split 1-5 failed in AdvisoryAI due to stale build outputs, re-run `dotnet test` for AdvisoryAI passed (207 tests). | DevOps |
| 2026-01-07 | Unit-split runs (projects 1-20) completed after AdvisoryAI rebuild; all 20 projects passed. | DevOps |
## Decisions & Risks ## Decisions & Risks
- Risk: local CI steps drift from pipeline definitions; mitigate with scheduled doc sync. - Risk: local CI steps drift from pipeline definitions; mitigate with scheduled doc sync.

View File

@@ -93,7 +93,7 @@ Quality gates are integrated into the main CI workflow:
# .gitea/workflows/build-test-deploy.yml # .gitea/workflows/build-test-deploy.yml
quality-gates: quality-gates:
runs-on: ubuntu-22.04 runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
needs: build-test needs: build-test
steps: steps:
- name: Reachability quality gate - name: Reachability quality gate

View File

@@ -44,7 +44,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
{ {
ArgumentNullException.ThrowIfNull(entry); ArgumentNullException.ThrowIfNull(entry);
var signatureIssuer = await EvaluateSignatureAndIssuerAsync(entry, bundle, cancellationToken).ConfigureAwait(false); var signatureIssuer = await EvaluateSignatureAndIssuerAsync(entry, bundle, evaluationTime, cancellationToken).ConfigureAwait(false);
var freshness = EvaluateFreshness(entry, evaluationTime); var freshness = EvaluateFreshness(entry, evaluationTime);
var transparency = EvaluateTransparency(entry); var transparency = EvaluateTransparency(entry);
var policy = EvaluatePolicy(entry, signatureIssuer.Signatures, signatureIssuer.Issuer, freshness, transparency, bundle is not null); var policy = EvaluatePolicy(entry, signatureIssuer.Signatures, signatureIssuer.Issuer, freshness, transparency, bundle is not null);
@@ -55,6 +55,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
private async Task<(SignatureEvaluationResult Signatures, IssuerEvaluationResult Issuer)> EvaluateSignatureAndIssuerAsync( private async Task<(SignatureEvaluationResult Signatures, IssuerEvaluationResult Issuer)> EvaluateSignatureAndIssuerAsync(
AttestorEntry entry, AttestorEntry entry,
AttestorSubmissionRequest.SubmissionBundle? bundle, AttestorSubmissionRequest.SubmissionBundle? bundle,
DateTimeOffset evaluationTime,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var signatureIssues = new List<string>(); var signatureIssues = new List<string>();
@@ -178,7 +179,7 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
break; break;
case "keyless": case "keyless":
var keylessResult = EvaluateKeylessSignature(entry, bundle, preAuth, signatureIssues, issuerIssues); var keylessResult = EvaluateKeylessSignature(entry, bundle, preAuth, signatureIssues, issuerIssues, evaluationTime);
verifiedSignatures = keylessResult.VerifiedSignatures; verifiedSignatures = keylessResult.VerifiedSignatures;
subjectAlternativeName = keylessResult.SubjectAlternativeName; subjectAlternativeName = keylessResult.SubjectAlternativeName;
break; break;
@@ -270,7 +271,8 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
AttestorSubmissionRequest.SubmissionBundle bundle, AttestorSubmissionRequest.SubmissionBundle bundle,
byte[] preAuthEncoding, byte[] preAuthEncoding,
List<string> signatureIssues, List<string> signatureIssues,
List<string> issuerIssues) List<string> issuerIssues,
DateTimeOffset evaluationTime)
{ {
if (bundle.CertificateChain.Count == 0) if (bundle.CertificateChain.Count == 0)
{ {
@@ -296,46 +298,47 @@ public sealed class AttestorVerificationEngine : IAttestorVerificationEngine
var leafCertificate = certificates[0]; var leafCertificate = certificates[0];
var subjectAltName = GetSubjectAlternativeNames(leafCertificate).FirstOrDefault(); var subjectAltName = GetSubjectAlternativeNames(leafCertificate).FirstOrDefault();
if (_options.Security.SignerIdentity.FulcioRoots.Count > 0) if (_options.Security.SignerIdentity.FulcioRoots.Count > 0)
{
using var chain = new X509Chain
{ {
using var chain = new X509Chain ChainPolicy =
{
ChainPolicy =
{ {
RevocationMode = X509RevocationMode.NoCheck, RevocationMode = X509RevocationMode.NoCheck,
VerificationFlags = X509VerificationFlags.NoFlag, VerificationFlags = X509VerificationFlags.NoFlag,
TrustMode = X509ChainTrustMode.CustomRootTrust TrustMode = X509ChainTrustMode.CustomRootTrust,
VerificationTime = evaluationTime.UtcDateTime
} }
}; };
foreach (var rootPath in _options.Security.SignerIdentity.FulcioRoots) foreach (var rootPath in _options.Security.SignerIdentity.FulcioRoots)
{
try
{ {
try if (File.Exists(rootPath))
{ {
if (File.Exists(rootPath)) var rootCertificate = X509CertificateLoader.LoadCertificateFromFile(rootPath);
{ chain.ChainPolicy.CustomTrustStore.Add(rootCertificate);
var rootCertificate = X509CertificateLoader.LoadCertificateFromFile(rootPath);
chain.ChainPolicy.CustomTrustStore.Add(rootCertificate);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to load Fulcio root {Root}", rootPath);
} }
} }
catch (Exception ex)
for (var i = 1; i < certificates.Count; i++)
{ {
chain.ChainPolicy.ExtraStore.Add(certificates[i]); _logger.LogWarning(ex, "Failed to load Fulcio root {Root}", rootPath);
}
if (!chain.Build(leafCertificate))
{
var status = string.Join(";", chain.ChainStatus.Select(s => s.StatusInformation.Trim())).Trim(';');
issuerIssues.Add(string.IsNullOrEmpty(status) ? "certificate_chain_untrusted" : $"certificate_chain_untrusted:{status}");
} }
} }
for (var i = 1; i < certificates.Count; i++)
{
chain.ChainPolicy.ExtraStore.Add(certificates[i]);
}
if (!chain.Build(leafCertificate))
{
var status = string.Join(";", chain.ChainStatus.Select(s => s.StatusInformation.Trim())).Trim(';');
issuerIssues.Add(string.IsNullOrEmpty(status) ? "certificate_chain_untrusted" : $"certificate_chain_untrusted:{status}");
}
}
if (_options.Security.SignerIdentity.AllowedSans.Count > 0) if (_options.Security.SignerIdentity.AllowedSans.Count > 0)
{ {
var sans = GetSubjectAlternativeNames(leafCertificate); var sans = GetSubjectAlternativeNames(leafCertificate);

View File

@@ -223,9 +223,12 @@ public class AttestationChainBuilderTests
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
result.LinksCreated.Should().HaveCount(3); result.LinksCreated.Should().HaveCount(3);
// Links should be created in layer order // Links should be created in layer order
result.LinksCreated[0].Metadata!.Annotations["layerIndex"].Should().Be("0"); Assert.NotNull(result.LinksCreated[0].Metadata?.Annotations);
result.LinksCreated[1].Metadata!.Annotations["layerIndex"].Should().Be("1"); Assert.NotNull(result.LinksCreated[1].Metadata?.Annotations);
result.LinksCreated[2].Metadata!.Annotations["layerIndex"].Should().Be("2"); Assert.NotNull(result.LinksCreated[2].Metadata?.Annotations);
result.LinksCreated[0].Metadata!.Annotations!["layerIndex"].Should().Be("0");
result.LinksCreated[1].Metadata!.Annotations!["layerIndex"].Should().Be("1");
result.LinksCreated[2].Metadata!.Annotations!["layerIndex"].Should().Be("2");
} }
[Fact] [Fact]

View File

@@ -319,7 +319,21 @@ public sealed class AttestorVerificationEngineTests
} }
private static X509Certificate2 EnsurePrivateKey(X509Certificate2 certificate, ECDsa key) private static X509Certificate2 EnsurePrivateKey(X509Certificate2 certificate, ECDsa key)
=> certificate.HasPrivateKey ? certificate : certificate.CopyWithPrivateKey(key); {
if (certificate.HasPrivateKey)
{
return certificate;
}
try
{
return certificate.CopyWithPrivateKey(key);
}
catch (InvalidOperationException)
{
return certificate;
}
}
private static string ToPem(X509Certificate2 certificate) private static string ToPem(X509Certificate2 certificate)
{ {

View File

@@ -65,35 +65,35 @@ public class AuthAbstractionsConstantsTests
[nameof(StellaOpsClaimTypes.SessionId)] = "sid" [nameof(StellaOpsClaimTypes.SessionId)] = "sid"
}; };
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Subject)], StellaOpsClaimTypes.Subject); Assert.Equal(StellaOpsClaimTypes.Subject, expected[nameof(StellaOpsClaimTypes.Subject)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Tenant)], StellaOpsClaimTypes.Tenant); Assert.Equal(StellaOpsClaimTypes.Tenant, expected[nameof(StellaOpsClaimTypes.Tenant)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Project)], StellaOpsClaimTypes.Project); Assert.Equal(StellaOpsClaimTypes.Project, expected[nameof(StellaOpsClaimTypes.Project)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ClientId)], StellaOpsClaimTypes.ClientId); Assert.Equal(StellaOpsClaimTypes.ClientId, expected[nameof(StellaOpsClaimTypes.ClientId)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ServiceAccount)], StellaOpsClaimTypes.ServiceAccount); Assert.Equal(StellaOpsClaimTypes.ServiceAccount, expected[nameof(StellaOpsClaimTypes.ServiceAccount)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.TokenId)], StellaOpsClaimTypes.TokenId); Assert.Equal(StellaOpsClaimTypes.TokenId, expected[nameof(StellaOpsClaimTypes.TokenId)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.AuthenticationMethod)], StellaOpsClaimTypes.AuthenticationMethod); Assert.Equal(StellaOpsClaimTypes.AuthenticationMethod, expected[nameof(StellaOpsClaimTypes.AuthenticationMethod)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Scope)], StellaOpsClaimTypes.Scope); Assert.Equal(StellaOpsClaimTypes.Scope, expected[nameof(StellaOpsClaimTypes.Scope)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.ScopeItem)], StellaOpsClaimTypes.ScopeItem); Assert.Equal(StellaOpsClaimTypes.ScopeItem, expected[nameof(StellaOpsClaimTypes.ScopeItem)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.Audience)], StellaOpsClaimTypes.Audience); Assert.Equal(StellaOpsClaimTypes.Audience, expected[nameof(StellaOpsClaimTypes.Audience)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.IdentityProvider)], StellaOpsClaimTypes.IdentityProvider); Assert.Equal(StellaOpsClaimTypes.IdentityProvider, expected[nameof(StellaOpsClaimTypes.IdentityProvider)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.OperatorReason)], StellaOpsClaimTypes.OperatorReason); Assert.Equal(StellaOpsClaimTypes.OperatorReason, expected[nameof(StellaOpsClaimTypes.OperatorReason)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.OperatorTicket)], StellaOpsClaimTypes.OperatorTicket); Assert.Equal(StellaOpsClaimTypes.OperatorTicket, expected[nameof(StellaOpsClaimTypes.OperatorTicket)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.QuotaReason)], StellaOpsClaimTypes.QuotaReason); Assert.Equal(StellaOpsClaimTypes.QuotaReason, expected[nameof(StellaOpsClaimTypes.QuotaReason)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.QuotaTicket)], StellaOpsClaimTypes.QuotaTicket); Assert.Equal(StellaOpsClaimTypes.QuotaTicket, expected[nameof(StellaOpsClaimTypes.QuotaTicket)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.BackfillReason)], StellaOpsClaimTypes.BackfillReason); Assert.Equal(StellaOpsClaimTypes.BackfillReason, expected[nameof(StellaOpsClaimTypes.BackfillReason)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.BackfillTicket)], StellaOpsClaimTypes.BackfillTicket); Assert.Equal(StellaOpsClaimTypes.BackfillTicket, expected[nameof(StellaOpsClaimTypes.BackfillTicket)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyDigest)], StellaOpsClaimTypes.PolicyDigest); Assert.Equal(StellaOpsClaimTypes.PolicyDigest, expected[nameof(StellaOpsClaimTypes.PolicyDigest)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyTicket)], StellaOpsClaimTypes.PolicyTicket); Assert.Equal(StellaOpsClaimTypes.PolicyTicket, expected[nameof(StellaOpsClaimTypes.PolicyTicket)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyReason)], StellaOpsClaimTypes.PolicyReason); Assert.Equal(StellaOpsClaimTypes.PolicyReason, expected[nameof(StellaOpsClaimTypes.PolicyReason)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackRunId)], StellaOpsClaimTypes.PackRunId); Assert.Equal(StellaOpsClaimTypes.PackRunId, expected[nameof(StellaOpsClaimTypes.PackRunId)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackGateId)], StellaOpsClaimTypes.PackGateId); Assert.Equal(StellaOpsClaimTypes.PackGateId, expected[nameof(StellaOpsClaimTypes.PackGateId)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PackPlanHash)], StellaOpsClaimTypes.PackPlanHash); Assert.Equal(StellaOpsClaimTypes.PackPlanHash, expected[nameof(StellaOpsClaimTypes.PackPlanHash)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.PolicyOperation)], StellaOpsClaimTypes.PolicyOperation); Assert.Equal(StellaOpsClaimTypes.PolicyOperation, expected[nameof(StellaOpsClaimTypes.PolicyOperation)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.IncidentReason)], StellaOpsClaimTypes.IncidentReason); Assert.Equal(StellaOpsClaimTypes.IncidentReason, expected[nameof(StellaOpsClaimTypes.IncidentReason)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityEnvironment)], StellaOpsClaimTypes.VulnerabilityEnvironment); Assert.Equal(StellaOpsClaimTypes.VulnerabilityEnvironment, expected[nameof(StellaOpsClaimTypes.VulnerabilityEnvironment)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityOwner)], StellaOpsClaimTypes.VulnerabilityOwner); Assert.Equal(StellaOpsClaimTypes.VulnerabilityOwner, expected[nameof(StellaOpsClaimTypes.VulnerabilityOwner)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.VulnerabilityBusinessTier)], StellaOpsClaimTypes.VulnerabilityBusinessTier); Assert.Equal(StellaOpsClaimTypes.VulnerabilityBusinessTier, expected[nameof(StellaOpsClaimTypes.VulnerabilityBusinessTier)]);
Assert.Equal(expected[nameof(StellaOpsClaimTypes.SessionId)], StellaOpsClaimTypes.SessionId); Assert.Equal(StellaOpsClaimTypes.SessionId, expected[nameof(StellaOpsClaimTypes.SessionId)]);
} }
[Trait("Category", TestCategories.Unit)] [Trait("Category", TestCategories.Unit)]

View File

@@ -72,7 +72,7 @@ public class ServiceCollectionExtensionsTests
using var provider = services.BuildServiceProvider(); using var provider = services.BuildServiceProvider();
var cache = provider.GetRequiredService<StellaOpsDiscoveryCache>(); var cache = provider.GetRequiredService<StellaOpsDiscoveryCache>();
var configuration = await cache.GetAsync(CancellationToken.None); var configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
Assert.Equal(2, attemptCount); Assert.Equal(2, attemptCount);

View File

@@ -47,12 +47,12 @@ public class StellaOpsDiscoveryCacheTests
var monitor = new TestOptionsMonitor<StellaOpsAuthClientOptions>(options); var monitor = new TestOptionsMonitor<StellaOpsAuthClientOptions>(options);
var cache = new StellaOpsDiscoveryCache(httpClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance); var cache = new StellaOpsDiscoveryCache(httpClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance);
var configuration = await cache.GetAsync(CancellationToken.None); var configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5)); timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5));
configuration = await cache.GetAsync(CancellationToken.None); configuration = await cache.GetAsync(TestContext.Current.CancellationToken);
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint); Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
Assert.Equal(2, callCount); Assert.Equal(2, callCount);
@@ -66,7 +66,7 @@ public class StellaOpsDiscoveryCacheTests
HttpRequestException? exception = null; HttpRequestException? exception = null;
try try
{ {
await cache.GetAsync(CancellationToken.None); await cache.GetAsync(TestContext.Current.CancellationToken);
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {

View File

@@ -52,12 +52,12 @@ public class StellaOpsJwksCacheTests
var discoveryCache = new StellaOpsDiscoveryCache(discoveryClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance); var discoveryCache = new StellaOpsDiscoveryCache(discoveryClient, monitor, timeProvider, NullLogger<StellaOpsDiscoveryCache>.Instance);
var jwksCache = new StellaOpsJwksCache(jwksClient, discoveryCache, monitor, timeProvider, NullLogger<StellaOpsJwksCache>.Instance); var jwksCache = new StellaOpsJwksCache(jwksClient, discoveryCache, monitor, timeProvider, NullLogger<StellaOpsJwksCache>.Instance);
var keys = await jwksCache.GetAsync(CancellationToken.None); var keys = await jwksCache.GetAsync(TestContext.Current.CancellationToken);
Assert.NotNull(keys); Assert.NotNull(keys);
timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5)); timeProvider.Advance(TimeSpan.FromMinutes(1) + TimeSpan.FromSeconds(5));
keys = await jwksCache.GetAsync(CancellationToken.None); keys = await jwksCache.GetAsync(TestContext.Current.CancellationToken);
Assert.NotNull(keys); Assert.NotNull(keys);
Assert.Equal(2, jwksCallCount); Assert.Equal(2, jwksCallCount);
@@ -68,7 +68,7 @@ public class StellaOpsJwksCacheTests
Assert.True(offlineExpiry < timeProvider.GetUtcNow()); Assert.True(offlineExpiry < timeProvider.GetUtcNow());
await Assert.ThrowsAsync<HttpRequestException>(() => jwksCache.GetAsync(CancellationToken.None)); await Assert.ThrowsAsync<HttpRequestException>(() => jwksCache.GetAsync(TestContext.Current.CancellationToken));
} }
private static HttpResponseMessage CreateJsonResponse(string json) private static HttpResponseMessage CreateJsonResponse(string json)

View File

@@ -38,7 +38,7 @@ public class StellaOpsAuthorityConfigurationManagerTests
var initialMetadataRequests = handler.MetadataRequests; var initialMetadataRequests = handler.MetadataRequests;
var initialJwksRequests = handler.JwksRequests; var initialJwksRequests = handler.JwksRequests;
var second = await manager.GetConfigurationAsync(CancellationToken.None); var second = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
// Cache must return same instance // Cache must return same instance
Assert.Same(first, second); Assert.Same(first, second);
@@ -70,11 +70,11 @@ public class StellaOpsAuthorityConfigurationManagerTests
timeProvider, timeProvider,
NullLogger<StellaOpsAuthorityConfigurationManager>.Instance); NullLogger<StellaOpsAuthorityConfigurationManager>.Instance);
var first = await manager.GetConfigurationAsync(CancellationToken.None); var first = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
timeProvider.Advance(TimeSpan.FromMinutes(2)); timeProvider.Advance(TimeSpan.FromMinutes(2));
var second = await manager.GetConfigurationAsync(CancellationToken.None); var second = await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
Assert.Same(first, second); Assert.Same(first, second);
Assert.Equal(2, handler.MetadataRequests); Assert.Equal(2, handler.MetadataRequests);
@@ -100,12 +100,12 @@ public class StellaOpsAuthorityConfigurationManagerTests
timeProvider, timeProvider,
NullLogger<StellaOpsAuthorityConfigurationManager>.Instance); NullLogger<StellaOpsAuthorityConfigurationManager>.Instance);
await manager.GetConfigurationAsync(CancellationToken.None); await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
var updated = CreateOptions("https://authority2.test"); var updated = CreateOptions("https://authority2.test");
optionsMonitor.Set(updated); optionsMonitor.Set(updated);
await manager.GetConfigurationAsync(CancellationToken.None); await manager.GetConfigurationAsync(TestContext.Current.CancellationToken);
Assert.Equal(2, handler.MetadataRequests); Assert.Equal(2, handler.MetadataRequests);
} }

View File

@@ -21,8 +21,8 @@ public sealed class InMemoryLdapClaimsCacheTests
["displayName"] = "Alice Example" ["displayName"] = "Alice Example"
}); });
await cache.SetAsync("uid=alice,ou=people,dc=example,dc=internal", claims, CancellationToken.None); await cache.SetAsync("uid=alice,ou=people,dc=example,dc=internal", claims, TestContext.Current.CancellationToken);
var fetched = await cache.GetAsync("uid=alice,ou=people,dc=example,dc=internal", CancellationToken.None); var fetched = await cache.GetAsync("uid=alice,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.NotNull(fetched); Assert.NotNull(fetched);
Assert.Contains("operators", fetched!.Roles); Assert.Contains("operators", fetched!.Roles);
@@ -35,10 +35,10 @@ public sealed class InMemoryLdapClaimsCacheTests
var timeProvider = new TestTimeProvider(new DateTimeOffset(2025, 11, 9, 6, 0, 0, TimeSpan.Zero)); var timeProvider = new TestTimeProvider(new DateTimeOffset(2025, 11, 9, 6, 0, 0, TimeSpan.Zero));
var cache = CreateCache(enabled: true, ttlSeconds: 60, timeProvider: timeProvider); var cache = CreateCache(enabled: true, ttlSeconds: 60, timeProvider: timeProvider);
await cache.SetAsync("uid=expired,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None); await cache.SetAsync("uid=expired,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
timeProvider.Advance(TimeSpan.FromMinutes(5)); timeProvider.Advance(TimeSpan.FromMinutes(5));
var fetched = await cache.GetAsync("uid=expired,ou=people,dc=example,dc=internal", CancellationToken.None); var fetched = await cache.GetAsync("uid=expired,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.Null(fetched); Assert.Null(fetched);
} }
@@ -48,11 +48,11 @@ public sealed class InMemoryLdapClaimsCacheTests
{ {
var cache = CreateCache(enabled: true, ttlSeconds: 600, maxEntries: 1); var cache = CreateCache(enabled: true, ttlSeconds: 600, maxEntries: 1);
await cache.SetAsync("uid=first,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None); await cache.SetAsync("uid=first,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
await cache.SetAsync("uid=second,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), CancellationToken.None); await cache.SetAsync("uid=second,ou=people,dc=example,dc=internal", new LdapCachedClaims(Array.Empty<string>(), new Dictionary<string, string>()), TestContext.Current.CancellationToken);
var first = await cache.GetAsync("uid=first,ou=people,dc=example,dc=internal", CancellationToken.None); var first = await cache.GetAsync("uid=first,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
var second = await cache.GetAsync("uid=second,ou=people,dc=example,dc=internal", CancellationToken.None); var second = await cache.GetAsync("uid=second,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.Null(first); Assert.Null(first);
Assert.NotNull(second); Assert.NotNull(second);

View File

@@ -41,7 +41,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity(); var identity = new ClaimsIdentity();
var context = CreateContext("uid=j.doe,ou=people,dc=example,dc=internal"); var context = CreateContext("uid=j.doe,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None); await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators"); Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators");
} }
@@ -74,7 +74,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity(); var identity = new ClaimsIdentity();
var context = CreateContext("uid=ops,ou=people,dc=example,dc=internal"); var context = CreateContext("uid=ops,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None); await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "incident"); Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "incident");
} }
@@ -107,7 +107,7 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity(); var identity = new ClaimsIdentity();
var context = CreateContext("uid=alice,ou=people,dc=example,dc=internal"); var context = CreateContext("uid=alice,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None); await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Alice Example"); Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Alice Example");
Assert.Contains(identity.Claims, claim => claim.Type == "email" && claim.Value == "alice@example.test"); Assert.Contains(identity.Claims, claim => claim.Type == "email" && claim.Value == "alice@example.test");
@@ -134,11 +134,11 @@ public class LdapClaimsEnricherTests
var identity = new ClaimsIdentity(); var identity = new ClaimsIdentity();
var context = CreateContext("uid=cached,ou=people,dc=example,dc=internal"); var context = CreateContext("uid=cached,ou=people,dc=example,dc=internal");
await enricher.EnrichAsync(identity, context, CancellationToken.None); await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators"); Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "operators");
Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Cached User"); Assert.Contains(identity.Claims, claim => claim.Type == "displayName" && claim.Value == "Cached User");
Assert.Equal(0, connection.Operations.Count); Assert.Empty(connection.Operations);
Assert.Equal(0, cache.SetCount); Assert.Equal(0, cache.SetCount);
} }

View File

@@ -25,7 +25,7 @@ public class LdapCapabilityProbeTests
checkClientProvisioning: true, checkClientProvisioning: true,
checkBootstrap: true, checkBootstrap: true,
options.CapabilityProbe.Timeout, options.CapabilityProbe.Timeout,
CancellationToken.None); TestContext.Current.CancellationToken);
Assert.True(snapshot.ClientProvisioningWritable); Assert.True(snapshot.ClientProvisioningWritable);
Assert.True(snapshot.BootstrapWritable); Assert.True(snapshot.BootstrapWritable);
@@ -47,7 +47,7 @@ public class LdapCapabilityProbeTests
checkClientProvisioning: true, checkClientProvisioning: true,
checkBootstrap: true, checkBootstrap: true,
options.CapabilityProbe.Timeout, options.CapabilityProbe.Timeout,
CancellationToken.None); TestContext.Current.CancellationToken);
Assert.False(snapshot.ClientProvisioningWritable); Assert.False(snapshot.ClientProvisioningWritable);
Assert.False(snapshot.BootstrapWritable); Assert.False(snapshot.BootstrapWritable);

View File

@@ -49,7 +49,7 @@ public sealed class LdapClientProvisioningStoreTests
allowedScopes: new[] { "signer.sign" }, allowedScopes: new[] { "signer.sign" },
allowedAudiences: new[] { "signer" }); allowedAudiences: new[] { "signer" });
var result = await store.CreateOrUpdateAsync(registration, CancellationToken.None); var result = await store.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.True(clientStore.Documents.ContainsKey("svc-bootstrap")); Assert.True(clientStore.Documents.ContainsKey("svc-bootstrap"));
@@ -88,7 +88,7 @@ public sealed class LdapClientProvisioningStoreTests
SecretHash = "hash" SecretHash = "hash"
}; };
var result = await store.DeleteAsync("svc-bootstrap", CancellationToken.None); var result = await store.DeleteAsync("svc-bootstrap", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.DoesNotContain("svc-bootstrap", clientStore.Documents.Keys); Assert.DoesNotContain("svc-bootstrap", clientStore.Documents.Keys);
@@ -124,7 +124,7 @@ public sealed class LdapClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" }, allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "signer.sign" }); allowedScopes: new[] { "signer.sign" });
var result = await store.CreateOrUpdateAsync(registration, CancellationToken.None); var result = await store.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.False(result.Succeeded); Assert.False(result.Succeeded);
Assert.Equal("disabled", result.ErrorCode); Assert.Equal("disabled", result.ErrorCode);

View File

@@ -44,7 +44,7 @@ public class LdapCredentialStoreTests
monitor, monitor,
new FakeLdapConnectionFactory(connection)); new FakeLdapConnectionFactory(connection));
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.Equal(2, bindCalls.Count); Assert.Equal(2, bindCalls.Count);
@@ -86,7 +86,7 @@ public class LdapCredentialStoreTests
monitor, monitor,
new FakeLdapConnectionFactory(connection)); new FakeLdapConnectionFactory(connection));
var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("J.Doe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.NotNull(result.User); Assert.NotNull(result.User);
@@ -119,7 +119,7 @@ public class LdapCredentialStoreTests
new FakeLdapConnectionFactory(connection), new FakeLdapConnectionFactory(connection),
delayAsync: (_, _) => Task.CompletedTask); delayAsync: (_, _) => Task.CompletedTask);
var result = await store.VerifyPasswordAsync("jdoe", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("jdoe", "Password1!", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.Equal(2, attempts); Assert.Equal(2, attempts);
@@ -142,7 +142,7 @@ public class LdapCredentialStoreTests
new FakeLdapConnectionFactory(connection), new FakeLdapConnectionFactory(connection),
delayAsync: (_, _) => Task.CompletedTask); delayAsync: (_, _) => Task.CompletedTask);
var result = await store.VerifyPasswordAsync("jdoe", "bad", CancellationToken.None); var result = await store.VerifyPasswordAsync("jdoe", "bad", TestContext.Current.CancellationToken);
Assert.False(result.Succeeded); Assert.False(result.Succeeded);
Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode); Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode);
@@ -176,7 +176,7 @@ public class LdapCredentialStoreTests
monitor, monitor,
new FakeLdapConnectionFactory(connection)); new FakeLdapConnectionFactory(connection));
var result = await store.FindBySubjectAsync("uid=j.doe,ou=people,dc=example,dc=internal", CancellationToken.None); var result = await store.FindBySubjectAsync("uid=j.doe,ou=people,dc=example,dc=internal", TestContext.Current.CancellationToken);
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("uid=j.doe,ou=people,dc=example,dc=internal", result!.SubjectId); Assert.Equal("uid=j.doe,ou=people,dc=example,dc=internal", result!.SubjectId);
@@ -201,7 +201,7 @@ public class LdapCredentialStoreTests
email: "bootstrap@example.internal", email: "bootstrap@example.internal",
requirePasswordReset: true); requirePasswordReset: true);
var result = await store.UpsertUserAsync(registration, CancellationToken.None); var result = await store.UpsertUserAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.Contains(connection.Operations, op => op.StartsWith("add:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase)); Assert.Contains(connection.Operations, op => op.StartsWith("add:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase));
@@ -234,7 +234,7 @@ public class LdapCredentialStoreTests
email: "bootstrap@example.internal", email: "bootstrap@example.internal",
requirePasswordReset: false); requirePasswordReset: false);
var result = await store.UpsertUserAsync(registration, CancellationToken.None); var result = await store.UpsertUserAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.Contains(connection.Operations, op => op.StartsWith("modify:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase)); Assert.Contains(connection.Operations, op => op.StartsWith("modify:uid=bootstrap.user", StringComparison.OrdinalIgnoreCase));

View File

@@ -61,7 +61,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("noname", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("noname", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeTrue("Missing displayName should not prevent authentication"); result.Succeeded.Should().BeTrue("Missing displayName should not prevent authentication");
@@ -88,7 +88,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("nomail", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("nomail", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeTrue("Missing mail should not prevent authentication"); result.Succeeded.Should().BeTrue("Missing mail should not prevent authentication");
@@ -113,7 +113,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("nogroups", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("nogroups", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeTrue("Empty memberOf should not prevent authentication"); result.Succeeded.Should().BeTrue("Empty memberOf should not prevent authentication");
@@ -135,7 +135,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("nonexistent", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("nonexistent", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Nonexistent user should fail authentication"); result.Succeeded.Should().BeFalse("Nonexistent user should fail authentication");
@@ -177,7 +177,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync("user", "WrongPassword!", CancellationToken.None); var result = await store.VerifyPasswordAsync("user", "WrongPassword!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Wrong password should fail authentication"); result.Succeeded.Should().BeFalse("Wrong password should fail authentication");
@@ -200,7 +200,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("malformed", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("malformed", "Password1!", TestContext.Current.CancellationToken);
// Assert - should handle gracefully (either succeed with warning or fail cleanly) // Assert - should handle gracefully (either succeed with warning or fail cleanly)
// The exact behavior depends on implementation // The exact behavior depends on implementation
@@ -225,7 +225,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None); Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
await act.Should().ThrowAsync<TimeoutException>(); await act.Should().ThrowAsync<TimeoutException>();
@@ -247,7 +247,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None); Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
await act.Should().ThrowAsync<InvalidOperationException>(); await act.Should().ThrowAsync<InvalidOperationException>();
@@ -303,7 +303,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("münchen-user", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("münchen-user", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeTrue("Unicode username should be handled"); result.Succeeded.Should().BeTrue("Unicode username should be handled");
@@ -329,7 +329,7 @@ public sealed class LdapConnectorResilienceTests
var store = CreateStore(options, connection); var store = CreateStore(options, connection);
// Act // Act
var result = await store.VerifyPasswordAsync("user+test", "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync("user+test", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeTrue("Special characters in DN should be handled"); result.Succeeded.Should().BeTrue("Special characters in DN should be handled");

View File

@@ -69,7 +69,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync(maliciousUsername, "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync(maliciousUsername, "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Injection attempt should fail"); result.Succeeded.Should().BeFalse("Injection attempt should fail");
@@ -103,7 +103,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync(emptyUsername, "Password1!", CancellationToken.None); var result = await store.VerifyPasswordAsync(emptyUsername, "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Empty username should be rejected"); result.Succeeded.Should().BeFalse("Empty username should be rejected");
@@ -120,7 +120,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync("user", null!, CancellationToken.None); var result = await store.VerifyPasswordAsync("user", null!, TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Null password should be rejected"); result.Succeeded.Should().BeFalse("Null password should be rejected");
@@ -137,7 +137,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync("user", "", CancellationToken.None); var result = await store.VerifyPasswordAsync("user", "", TestContext.Current.CancellationToken);
// Assert // Assert
result.Succeeded.Should().BeFalse("Empty password should be rejected"); result.Succeeded.Should().BeFalse("Empty password should be rejected");
@@ -169,7 +169,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", CancellationToken.None); Func<Task> act = async () => await store.VerifyPasswordAsync("user", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
await act.Should().ThrowAsync<InvalidOperationException>(); await act.Should().ThrowAsync<InvalidOperationException>();
@@ -201,7 +201,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
await store.VerifyPasswordAsync("targetuser", "Password1!", CancellationToken.None); await store.VerifyPasswordAsync("targetuser", "Password1!", TestContext.Current.CancellationToken);
// Assert // Assert
bindDns.Should().HaveCountGreaterThanOrEqualTo(2, "Should bind as service then as user"); bindDns.Should().HaveCountGreaterThanOrEqualTo(2, "Should bind as service then as user");
@@ -289,7 +289,7 @@ public sealed class LdapConnectorSecurityTests
// Act // Act
try try
{ {
await store.VerifyPasswordAsync("user", "SuperSecret123!", CancellationToken.None); await store.VerifyPasswordAsync("user", "SuperSecret123!", TestContext.Current.CancellationToken);
} }
catch catch
{ {
@@ -308,7 +308,7 @@ public sealed class LdapConnectorSecurityTests
var store = CreateStore(options, new FakeLdapConnectionFactory(connection)); var store = CreateStore(options, new FakeLdapConnectionFactory(connection));
// Act // Act
var result = await store.VerifyPasswordAsync("user", "MyPassword123", CancellationToken.None); var result = await store.VerifyPasswordAsync("user", "MyPassword123", TestContext.Current.CancellationToken);
// Assert // Assert
var resultString = result.ToString(); var resultString = result.ToString();

View File

@@ -112,13 +112,13 @@ public sealed class LdapConnectorSnapshotTests
{ {
// Arrange // Arrange
var fixtureFiles = Directory.Exists(FixturesPath) var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).ToList() ? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath) var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json") ? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f).Replace(".canonical", "")) .Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.ToList() .Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
// Assert // Assert

View File

@@ -52,7 +52,7 @@ public sealed class OidcCredentialStoreTests
username: "user@example.com", username: "user@example.com",
signingCredentials: new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256)); signingCredentials: new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256));
var result = await store.VerifyPasswordAsync("user@example.com", token, CancellationToken.None); var result = await store.VerifyPasswordAsync("user@example.com", token, TestContext.Current.CancellationToken);
Assert.False(result.Succeeded); Assert.False(result.Succeeded);
Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode); Assert.Equal(AuthorityCredentialFailureCode.InvalidCredentials, result.FailureCode);
@@ -102,10 +102,10 @@ public sealed class OidcCredentialStoreTests
username: "user2@example.com", username: "user2@example.com",
signingCredentials: new SigningCredentials(rsaKey, SecurityAlgorithms.RsaSha256)); signingCredentials: new SigningCredentials(rsaKey, SecurityAlgorithms.RsaSha256));
var result = await storeA.VerifyPasswordAsync("user2@example.com", token, CancellationToken.None); var result = await storeA.VerifyPasswordAsync("user2@example.com", token, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
var cached = await storeB.FindBySubjectAsync("user-2", CancellationToken.None); var cached = await storeB.FindBySubjectAsync("user-2", TestContext.Current.CancellationToken);
Assert.Null(cached); Assert.Null(cached);
} }
@@ -220,8 +220,8 @@ public sealed class OidcCredentialStoreTests
public OidcPluginOptions CurrentValue => options.Values.First(); public OidcPluginOptions CurrentValue => options.Values.First();
public OidcPluginOptions Get(string name) public OidcPluginOptions Get(string? name)
=> options.TryGetValue(name, out var value) ? value : options.Values.First(); => name is not null && options.TryGetValue(name, out var value) ? value : options.Values.First();
public IDisposable OnChange(Action<OidcPluginOptions, string> listener) public IDisposable OnChange(Action<OidcPluginOptions, string> listener)
=> new NoopDisposable(); => new NoopDisposable();

View File

@@ -23,7 +23,7 @@ public sealed class OidcIdentityProviderPluginTests
{ {
var (plugin, _) = CreatePlugin(HttpStatusCode.OK); var (plugin, _) = CreatePlugin(HttpStatusCode.OK);
var result = await plugin.CheckHealthAsync(CancellationToken.None); var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status); Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status);
} }
@@ -33,7 +33,7 @@ public sealed class OidcIdentityProviderPluginTests
{ {
var (plugin, _) = CreatePlugin(HttpStatusCode.ServiceUnavailable); var (plugin, _) = CreatePlugin(HttpStatusCode.ServiceUnavailable);
var result = await plugin.CheckHealthAsync(CancellationToken.None); var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status); Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status);
} }
@@ -132,7 +132,7 @@ public sealed class OidcIdentityProviderPluginTests
public OidcPluginOptions CurrentValue => options; public OidcPluginOptions CurrentValue => options;
public OidcPluginOptions Get(string name) public OidcPluginOptions Get(string? name)
=> string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options; => string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options;
public IDisposable OnChange(Action<OidcPluginOptions, string> listener) public IDisposable OnChange(Action<OidcPluginOptions, string> listener)

View File

@@ -129,13 +129,13 @@ public sealed class OidcConnectorSnapshotTests
{ {
// Arrange // Arrange
var fixtureFiles = Directory.Exists(FixturesPath) var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).ToList() ? Directory.EnumerateFiles(FixturesPath, "*.json").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath) var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json") ? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", "")) .Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.ToList() .Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
// Assert // Assert

View File

@@ -23,7 +23,7 @@ public sealed class SamlIdentityProviderPluginTests
{ {
var plugin = CreatePlugin(HttpStatusCode.OK); var plugin = CreatePlugin(HttpStatusCode.OK);
var result = await plugin.CheckHealthAsync(CancellationToken.None); var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status); Assert.Equal(AuthorityPluginHealthStatus.Healthy, result.Status);
} }
@@ -33,7 +33,7 @@ public sealed class SamlIdentityProviderPluginTests
{ {
var plugin = CreatePlugin(HttpStatusCode.ServiceUnavailable); var plugin = CreatePlugin(HttpStatusCode.ServiceUnavailable);
var result = await plugin.CheckHealthAsync(CancellationToken.None); var result = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status); Assert.Equal(AuthorityPluginHealthStatus.Degraded, result.Status);
} }
@@ -130,7 +130,7 @@ public sealed class SamlIdentityProviderPluginTests
public SamlPluginOptions CurrentValue => options; public SamlPluginOptions CurrentValue => options;
public SamlPluginOptions Get(string name) public SamlPluginOptions Get(string? name)
=> string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options; => string.Equals(name, pluginName, StringComparison.Ordinal) ? options : options;
public IDisposable OnChange(Action<SamlPluginOptions, string> listener) public IDisposable OnChange(Action<SamlPluginOptions, string> listener)

View File

@@ -122,13 +122,13 @@ public sealed class SamlConnectorSnapshotTests
{ {
// Arrange // Arrange
var fixtureFiles = Directory.Exists(FixturesPath) var fixtureFiles = Directory.Exists(FixturesPath)
? Directory.EnumerateFiles(FixturesPath, "*.xml").Select(Path.GetFileNameWithoutExtension).ToList() ? Directory.EnumerateFiles(FixturesPath, "*.xml").Select(Path.GetFileNameWithoutExtension).Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
var expectedFiles = Directory.Exists(ExpectedPath) var expectedFiles = Directory.Exists(ExpectedPath)
? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json") ? Directory.EnumerateFiles(ExpectedPath, "*.canonical.json")
.Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", "")) .Select(f => Path.GetFileNameWithoutExtension(f)?.Replace(".canonical", ""))
.ToList() .Where(n => n is not null).Cast<string>().ToList()
: new List<string>(); : new List<string>();
// Assert // Assert

View File

@@ -39,7 +39,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: null, failureCode: null,
reason: null, reason: null,
properties: Array.Empty<AuthEventProperty>(), properties: Array.Empty<AuthEventProperty>(),
CancellationToken.None); TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records); var record = Assert.Single(sink.Records);
Assert.Equal("authority.plugin.standard.password_verification", record.EventType); Assert.Equal("authority.plugin.standard.password_verification", record.EventType);
@@ -92,7 +92,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.InvalidCredentials, failureCode: AuthorityCredentialFailureCode.InvalidCredentials,
reason: "Invalid credentials.", reason: "Invalid credentials.",
properties, properties,
CancellationToken.None); TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records); var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.Failure, record.Outcome); Assert.Equal(AuthEventOutcome.Failure, record.Outcome);
@@ -147,7 +147,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.LockedOut, failureCode: AuthorityCredentialFailureCode.LockedOut,
reason: "Account locked.", reason: "Account locked.",
properties, properties,
CancellationToken.None); TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records); var record = Assert.Single(sink.Records);
Assert.Equal(AuthEventOutcome.LockedOut, record.Outcome); Assert.Equal(AuthEventOutcome.LockedOut, record.Outcome);
@@ -189,7 +189,7 @@ public class StandardCredentialAuditLoggerTests
failureCode: AuthorityCredentialFailureCode.RequiresMfa, failureCode: AuthorityCredentialFailureCode.RequiresMfa,
reason: "MFA required.", reason: "MFA required.",
properties: null, properties: null,
CancellationToken.None); TestContext.Current.CancellationToken);
var record = Assert.Single(sink.Records); var record = Assert.Single(sink.Records);
var property = Assert.Single(record.Properties); var property = Assert.Single(record.Properties);

View File

@@ -43,7 +43,7 @@ public class StandardClaimsEnricherTests
var identity = new ClaimsIdentity(); var identity = new ClaimsIdentity();
var enricher = new StandardClaimsEnricher(); var enricher = new StandardClaimsEnricher();
await enricher.EnrichAsync(identity, context, CancellationToken.None); await enricher.EnrichAsync(identity, context, TestContext.Current.CancellationToken);
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "admin"); Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "admin");
Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "ops"); Assert.Contains(identity.Claims, claim => claim.Type == ClaimTypes.Role && claim.Value == "ops");

View File

@@ -32,7 +32,7 @@ public class StandardClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" }, allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "scopeA" }); allowedScopes: new[] { "scopeA" });
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None); var result = await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.True(store.Documents.TryGetValue("bootstrap-client", out var document)); Assert.True(store.Documents.TryGetValue("bootstrap-client", out var document));
@@ -40,7 +40,7 @@ public class StandardClientProvisioningStoreTests
Assert.Equal(AuthoritySecretHasher.ComputeHash("SuperSecret1!"), document!.SecretHash); Assert.Equal(AuthoritySecretHasher.ComputeHash("SuperSecret1!"), document!.SecretHash);
Assert.Equal("standard", document.Plugin); Assert.Equal("standard", document.Plugin);
var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", CancellationToken.None); var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor); Assert.NotNull(descriptor);
Assert.Equal("bootstrap-client", descriptor!.ClientId); Assert.Equal("bootstrap-client", descriptor!.ClientId);
Assert.True(descriptor.Confidential); Assert.True(descriptor.Confidential);
@@ -65,13 +65,13 @@ public class StandardClientProvisioningStoreTests
allowedScopes: new[] { "scopeA" }, allowedScopes: new[] { "scopeA" },
tenant: " Tenant-Alpha " ); tenant: " Tenant-Alpha " );
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None); await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(store.Documents.TryGetValue("tenant-client", out var document)); Assert.True(store.Documents.TryGetValue("tenant-client", out var document));
Assert.NotNull(document); Assert.NotNull(document);
Assert.Equal("tenant-alpha", document!.Properties[AuthorityClientMetadataKeys.Tenant]); Assert.Equal("tenant-alpha", document!.Properties[AuthorityClientMetadataKeys.Tenant]);
var descriptor = await provisioning.FindByClientIdAsync("tenant-client", CancellationToken.None); var descriptor = await provisioning.FindByClientIdAsync("tenant-client", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor); Assert.NotNull(descriptor);
Assert.Equal("tenant-alpha", descriptor!.Tenant); Assert.Equal("tenant-alpha", descriptor!.Tenant);
} }
@@ -92,14 +92,14 @@ public class StandardClientProvisioningStoreTests
allowedScopes: new[] { "signer.sign" }, allowedScopes: new[] { "signer.sign" },
allowedAudiences: new[] { "attestor", "signer" }); allowedAudiences: new[] { "attestor", "signer" });
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None); var result = await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.True(store.Documents.TryGetValue("signer", out var document)); Assert.True(store.Documents.TryGetValue("signer", out var document));
Assert.NotNull(document); Assert.NotNull(document);
Assert.Equal("attestor signer", document!.Properties[AuthorityClientMetadataKeys.Audiences]); Assert.Equal("attestor signer", document!.Properties[AuthorityClientMetadataKeys.Audiences]);
var descriptor = await provisioning.FindByClientIdAsync("signer", CancellationToken.None); var descriptor = await provisioning.FindByClientIdAsync("signer", TestContext.Current.CancellationToken);
Assert.NotNull(descriptor); Assert.NotNull(descriptor);
Assert.Equal(new[] { "attestor", "signer" }, descriptor!.AllowedAudiences.OrderBy(value => value, StringComparer.Ordinal)); Assert.Equal(new[] { "attestor", "signer" }, descriptor!.AllowedAudiences.OrderBy(value => value, StringComparer.Ordinal));
} }
@@ -132,7 +132,7 @@ public class StandardClientProvisioningStoreTests
allowedAudiences: new[] { "signer" }, allowedAudiences: new[] { "signer" },
certificateBindings: new[] { bindingRegistration }); certificateBindings: new[] { bindingRegistration });
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None); await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
Assert.True(store.Documents.TryGetValue("mtls-client", out var document)); Assert.True(store.Documents.TryGetValue("mtls-client", out var document));
Assert.NotNull(document); Assert.NotNull(document);
@@ -164,9 +164,9 @@ public class StandardClientProvisioningStoreTests
allowedGrantTypes: new[] { "client_credentials" }, allowedGrantTypes: new[] { "client_credentials" },
allowedScopes: new[] { "scopeA" }); allowedScopes: new[] { "scopeA" });
await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None); await provisioning.CreateOrUpdateAsync(registration, TestContext.Current.CancellationToken);
var result = await provisioning.DeleteAsync("delete-me", CancellationToken.None); var result = await provisioning.DeleteAsync("delete-me", TestContext.Current.CancellationToken);
Assert.True(result.Succeeded); Assert.True(result.Succeeded);
Assert.False(store.Documents.ContainsKey("delete-me")); Assert.False(store.Documents.ContainsKey("delete-me"));

View File

@@ -58,7 +58,7 @@ public class StandardIdentityProviderPluginTests
new StandardClaimsEnricher(), new StandardClaimsEnricher(),
NullLogger<StandardIdentityProviderPlugin>.Instance); NullLogger<StandardIdentityProviderPlugin>.Instance);
var health = await plugin.CheckHealthAsync(CancellationToken.None); var health = await plugin.CheckHealthAsync(TestContext.Current.CancellationToken);
Assert.Equal(AuthorityPluginHealthStatus.Healthy, health.Status); Assert.Equal(AuthorityPluginHealthStatus.Healthy, health.Status);
} }

View File

@@ -67,7 +67,7 @@ public class StandardPluginBootstrapperTests
using var provider = services.BuildServiceProvider(); using var provider = services.BuildServiceProvider();
var bootstrapper = provider.GetRequiredService<StandardPluginBootstrapper>(); var bootstrapper = provider.GetRequiredService<StandardPluginBootstrapper>();
var exception = await Record.ExceptionAsync(() => bootstrapper.StartAsync(CancellationToken.None)); var exception = await Record.ExceptionAsync(() => bootstrapper.StartAsync(TestContext.Current.CancellationToken));
Assert.Null(exception); Assert.Null(exception);
} }

View File

@@ -228,6 +228,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
var updated = await store.UpsertUserAsync(update, CancellationToken.None); var updated = await store.UpsertUserAsync(update, CancellationToken.None);
Assert.True(updated.Succeeded); Assert.True(updated.Succeeded);
Assert.NotNull(updated.Value);
Assert.Contains("editor", updated.Value.Roles); Assert.Contains("editor", updated.Value.Roles);
Assert.Contains("admin", updated.Value.Roles); Assert.Contains("admin", updated.Value.Roles);
Assert.Equal("us", updated.Value.Attributes["region"]); Assert.Equal("us", updated.Value.Attributes["region"]);
@@ -250,6 +251,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
var created = await store.UpsertUserAsync(registration, CancellationToken.None); var created = await store.UpsertUserAsync(registration, CancellationToken.None);
Assert.True(created.Succeeded); Assert.True(created.Succeeded);
Assert.NotNull(created.Value);
var found = await store.FindBySubjectAsync(created.Value.SubjectId, CancellationToken.None); var found = await store.FindBySubjectAsync(created.Value.SubjectId, CancellationToken.None);
Assert.NotNull(found); Assert.NotNull(found);

View File

@@ -115,7 +115,7 @@ public sealed class AdvisoryAiRemoteInferenceEndpointTests : IClassFixture<Autho
var expectedHash = ComputeSha256(payload.Prompt); var expectedHash = ComputeSha256(payload.Prompt);
Assert.Equal(expectedHash, body["prompt_hash"]); Assert.Equal(expectedHash, body["prompt_hash"]);
var doc = Assert.Single(lastLoginAttemptStore!.Records.Where(record => record.EventType == "authority.advisory_ai.remote_inference")); var doc = Assert.Single(lastLoginAttemptStore!.Records, record => record.EventType == "authority.advisory_ai.remote_inference");
Assert.Equal("authority.advisory_ai.remote_inference", doc.EventType); Assert.Equal("authority.advisory_ai.remote_inference", doc.EventType);
var properties = doc.Properties.ToDictionary(p => p.Name, p => p.Value); var properties = doc.Properties.ToDictionary(p => p.Name, p => p.Value);
Assert.Equal(expectedHash, properties["advisory_ai.prompt.hash"]); Assert.Equal(expectedHash, properties["advisory_ai.prompt.hash"]);

View File

@@ -77,7 +77,7 @@ public class AuthorityAuditSinkTests
Assert.Equal(record.OccurredAt, document.OccurredAt); Assert.Equal(record.OccurredAt, document.OccurredAt);
Assert.Equal(new[] { "openid", "profile" }, document.Scopes); Assert.Equal(new[] { "openid", "profile" }, document.Scopes);
var pluginProperty = Assert.Single(document.Properties.Where(property => property.Name == "plugin.failed_attempts")); var pluginProperty = Assert.Single(document.Properties, property => property.Name == "plugin.failed_attempts");
Assert.Equal("0", pluginProperty.Value); Assert.Equal("0", pluginProperty.Value);
Assert.Equal("none", pluginProperty.Classification); Assert.Equal("none", pluginProperty.Classification);

View File

@@ -2926,9 +2926,9 @@ public class ClientCredentialsHandlersTests
Assert.False(validateContext.IsRejected); Assert.False(validateContext.IsRejected);
Assert.False(validateContext.Transaction.Properties.ContainsKey(AuthorityOpenIddictConstants.SenderConstraintProperty)); Assert.False(validateContext.Transaction.Properties.ContainsKey(AuthorityOpenIddictConstants.SenderConstraintProperty));
var bypassEvent = Assert.Single(auditSink.Events.Where(record => record.EventType == "authority.dpop.proof.bypass")); var bypassEvent = Assert.Single(auditSink.Events, record => record.EventType == "authority.dpop.proof.bypass");
Assert.Equal(AuthEventOutcome.Success, bypassEvent.Outcome); Assert.Equal(AuthEventOutcome.Success, bypassEvent.Outcome);
var reasonProperty = Assert.Single(bypassEvent.Properties.Where(property => property.Name == "dpop.reason_code")); var reasonProperty = Assert.Single(bypassEvent.Properties, property => property.Name == "dpop.reason_code");
Assert.Equal("bypass", reasonProperty.Value.Value); Assert.Equal("bypass", reasonProperty.Value.Value);
} }
@@ -3387,7 +3387,7 @@ public class ClientCredentialsHandlersTests
var grantEvent = authSink.Events.LastOrDefault(evt => evt.EventType == "authority.client_credentials.grant"); var grantEvent = authSink.Events.LastOrDefault(evt => evt.EventType == "authority.client_credentials.grant");
Assert.NotNull(grantEvent); Assert.NotNull(grantEvent);
var serviceProperty = Assert.Single(grantEvent!.Properties.Where(prop => prop.Name == "delegation.service_account")); var serviceProperty = Assert.Single(grantEvent!.Properties, prop => prop.Name == "delegation.service_account");
Assert.Equal(serviceAccount.AccountId, serviceProperty.Value.Value); Assert.Equal(serviceAccount.AccountId, serviceProperty.Value.Value);
var actorPropertyValues = grantEvent.Properties var actorPropertyValues = grantEvent.Properties

View File

@@ -22,6 +22,9 @@ namespace StellaOps.Authority.ConfigDiff.Tests;
[Trait("BlastRadius", TestCategories.BlastRadius.Auth)] [Trait("BlastRadius", TestCategories.BlastRadius.Auth)]
public class AuthorityConfigDiffTests : ConfigDiffTestBase public class AuthorityConfigDiffTests : ConfigDiffTestBase
{ {
private static readonly DateTimeOffset SnapshotTimestamp =
new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AuthorityConfigDiffTests"/> class. /// Initializes a new instance of the <see cref="AuthorityConfigDiffTests"/> class.
/// </summary> /// </summary>
@@ -61,7 +64,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
async config => await GetSessionBehaviorAsync(config), async config => await GetSessionBehaviorAsync(config),
async config => await GetRefreshBehaviorAsync(config), async config => await GetRefreshBehaviorAsync(config),
async config => await GetAuthenticationBehaviorAsync(config) async config => await GetAuthenticationBehaviorAsync(config)
]); ],
ct: TestContext.Current.CancellationToken);
// Assert // Assert
result.IsSuccess.Should().BeTrue( result.IsSuccess.Should().BeTrue(
@@ -93,7 +97,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
changedConfig, changedConfig,
getBehavior: async config => await CaptureSessionBehaviorAsync(config), getBehavior: async config => await CaptureSessionBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta, computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta); expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert // Assert
result.IsSuccess.Should().BeTrue( result.IsSuccess.Should().BeTrue(
@@ -119,7 +124,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
[ [
async config => await GetSessionBehaviorAsync(config), async config => await GetSessionBehaviorAsync(config),
async config => await GetPasswordPolicyBehaviorAsync(config) async config => await GetPasswordPolicyBehaviorAsync(config)
]); ],
ct: TestContext.Current.CancellationToken);
// Assert // Assert
result.IsSuccess.Should().BeTrue( result.IsSuccess.Should().BeTrue(
@@ -151,7 +157,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
changedConfig, changedConfig,
getBehavior: async config => await CapturePasswordPolicyBehaviorAsync(config), getBehavior: async config => await CapturePasswordPolicyBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta, computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta); expectedDelta: expectedDelta,
ct: TestContext.Current.CancellationToken);
// Assert // Assert
result.IsSuccess.Should().BeTrue(); result.IsSuccess.Should().BeTrue();
@@ -176,7 +183,8 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
[ [
async config => await GetTokenBehaviorAsync(config), async config => await GetTokenBehaviorAsync(config),
async config => await GetSessionBehaviorAsync(config) async config => await GetSessionBehaviorAsync(config)
]); ],
ct: TestContext.Current.CancellationToken);
// Assert // Assert
result.IsSuccess.Should().BeTrue( result.IsSuccess.Should().BeTrue(
@@ -216,11 +224,11 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
ConfigurationId: $"sessions-{config.MaxConcurrentSessions}", ConfigurationId: $"sessions-{config.MaxConcurrentSessions}",
Behaviors: Behaviors:
[ [
new CapturedBehavior("SessionLimit", config.MaxConcurrentSessions.ToString(), DateTimeOffset.UtcNow), new CapturedBehavior("SessionLimit", config.MaxConcurrentSessions.ToString(), SnapshotTimestamp),
new CapturedBehavior("ConcurrencyPolicy", new CapturedBehavior("ConcurrencyPolicy",
config.MaxConcurrentSessions > 5 ? "permissive" : "restrictive", DateTimeOffset.UtcNow) config.MaxConcurrentSessions > 5 ? "permissive" : "restrictive", SnapshotTimestamp)
], ],
CapturedAt: DateTimeOffset.UtcNow); CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot); return Task.FromResult(snapshot);
} }
@@ -232,11 +240,11 @@ public class AuthorityConfigDiffTests : ConfigDiffTestBase
Behaviors: Behaviors:
[ [
new CapturedBehavior("PasswordComplexity", new CapturedBehavior("PasswordComplexity",
config.MinPasswordLength >= 12 ? "enhanced" : "standard", DateTimeOffset.UtcNow), config.MinPasswordLength >= 12 ? "enhanced" : "standard", SnapshotTimestamp),
new CapturedBehavior("ValidationRejectionRate", new CapturedBehavior("ValidationRejectionRate",
config.MinPasswordLength >= 12 ? "increase" : "standard", DateTimeOffset.UtcNow) config.MinPasswordLength >= 12 ? "increase" : "standard", SnapshotTimestamp)
], ],
CapturedAt: DateTimeOffset.UtcNow); CapturedAt: SnapshotTimestamp);
return Task.FromResult(snapshot); return Task.FromResult(snapshot);
} }

View File

@@ -33,7 +33,7 @@ public sealed class AuthorityPostgresFixture : PostgresIntegrationFixture, IColl
public PostgresOptions CreateOptions() public PostgresOptions CreateOptions()
{ {
var options = Fixture.CreateOptions(); var options = Fixture.CreateOptions();
options.SchemaName = SchemaName; options.SchemaName = AuthorityDataSource.DefaultSchemaName;
options.MaxPoolSize = 10; options.MaxPoolSize = 10;
options.MinPoolSize = 0; options.MinPoolSize = 0;
return options; return options;

View File

@@ -1,5 +1,4 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -7,6 +6,7 @@ using Npgsql;
using StellaOps.Authority.Core.Verdicts; using StellaOps.Authority.Core.Verdicts;
using StellaOps.Authority.Persistence.Postgres; using StellaOps.Authority.Persistence.Postgres;
using StellaOps.TestKit; using StellaOps.TestKit;
using System.Text.Json;
using Xunit; using Xunit;
namespace StellaOps.Authority.Persistence.Tests; namespace StellaOps.Authority.Persistence.Tests;
@@ -37,7 +37,7 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
[Fact] [Fact]
public async Task StoreAndGetById_RoundTripsManifest() public async Task StoreAndGetById_RoundTripsManifest()
{ {
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T10:00:00Z"); var evaluatedAt = new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero);
var manifest = CreateManifest("tenant-1", "manifest-001", evaluatedAt, VexStatus.NotAffected); var manifest = CreateManifest("tenant-1", "manifest-001", evaluatedAt, VexStatus.NotAffected);
await _store.StoreAsync(manifest); await _store.StoreAsync(manifest);
@@ -58,12 +58,15 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
[Fact] [Fact]
public async Task StoreAsync_WritesStringEnumJson() public async Task StoreAsync_WritesStringEnumJson()
{ {
var evaluatedAt = DateTimeOffset.Parse("2025-01-15T11:00:00Z"); var evaluatedAt = new DateTimeOffset(2025, 1, 15, 11, 0, 0, TimeSpan.Zero);
var manifest = CreateManifest("tenant-2", "manifest-002", evaluatedAt, VexStatus.UnderInvestigation); var manifest = CreateManifest("tenant-2", "manifest-002", evaluatedAt, VexStatus.UnderInvestigation);
await _store.StoreAsync(manifest); await _store.StoreAsync(manifest);
await using var conn = await _dataSource.OpenConnectionAsync(manifest.Tenant, "reader", CancellationToken.None); await using var conn = await _dataSource.OpenConnectionAsync(
manifest.Tenant,
"reader",
TestContext.Current.CancellationToken);
await using var cmd = new NpgsqlCommand(""" await using var cmd = new NpgsqlCommand("""
SELECT result_json::text SELECT result_json::text
FROM verdict_manifests FROM verdict_manifests
@@ -77,7 +80,8 @@ public sealed class VerdictManifestStoreTests : IAsyncLifetime
var json = (string?)await cmd.ExecuteScalarAsync(); var json = (string?)await cmd.ExecuteScalarAsync();
json.Should().NotBeNull(); json.Should().NotBeNull();
json.Should().Contain("\"status\":\"under_investigation\""); using var document = JsonDocument.Parse(json!);
document.RootElement.GetProperty("status").GetString().Should().Be("under_investigation");
} }
private static VerdictManifest CreateManifest(string tenant, string manifestId, DateTimeOffset evaluatedAt, VexStatus status) private static VerdictManifest CreateManifest(string tenant, string manifestId, DateTimeOffset evaluatedAt, VexStatus status)

View File

@@ -223,7 +223,7 @@ public sealed class CommandHandlersTests
Assert.Equal("scan-missing", backend.LastEntryTraceScanId); Assert.Equal("scan-missing", backend.LastEntryTraceScanId);
Assert.Contains("No EntryTrace data", output.Combined, StringComparison.OrdinalIgnoreCase); Assert.Contains("No EntryTrace data", output.Combined, StringComparison.OrdinalIgnoreCase);
var warning = Assert.Single(loggerProvider.Entries.Where(entry => entry.Level == LogLevel.Warning)); var warning = Assert.Single(loggerProvider.Entries, entry => entry.Level == LogLevel.Warning);
Assert.Contains("No EntryTrace data", warning.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("No EntryTrace data", warning.Message, StringComparison.OrdinalIgnoreCase);
} }
finally finally

View File

@@ -1,5 +1,7 @@
## Release History ; Unshipped analyzer releases
### Unreleased ### New Rules
- CONCELIER0004: Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces; require sandboxed `IHttpClientFactory` to enforce allow/deny lists. Exempts test assemblies and uses symbol-based namespace matching. Rule ID | Category | Severity | Notes
--------|----------|----------|------
CONCELIER0004 | Sandbox | Warning | Flag direct `new HttpClient()` usage inside `StellaOps.Concelier.Connector*` namespaces

View File

@@ -15,7 +15,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
private static readonly DiagnosticDescriptor Rule = new( private static readonly DiagnosticDescriptor Rule = new(
id: DiagnosticId, id: DiagnosticId,
title: "Connector HTTP clients must use sandboxed factory", title: "Connector HTTP clients must use sandboxed factory",
messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors.", messageFormat: "Use IHttpClientFactory or connector sandbox helpers instead of 'new HttpClient()' inside Concelier connectors",
category: "Sandbox", category: "Sandbox",
defaultSeverity: DiagnosticSeverity.Warning, defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true, isEnabledByDefault: true,
@@ -73,7 +73,7 @@ public sealed class ConnectorHttpClientSandboxAnalyzer : DiagnosticAnalyzer
return false; return false;
} }
return assemblyName.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase) return assemblyName!.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase)
|| assemblyName.EndsWith(".Test", StringComparison.OrdinalIgnoreCase) || assemblyName.EndsWith(".Test", StringComparison.OrdinalIgnoreCase)
|| assemblyName.EndsWith(".Testing", StringComparison.OrdinalIgnoreCase); || assemblyName.EndsWith(".Testing", StringComparison.OrdinalIgnoreCase);
} }

View File

@@ -1,9 +1,7 @@
## Release History ; Unshipped analyzer releases
### Unreleased ### New Rules
#### New Rules Rule ID | Category | Severity | Notes
--------|----------|----------|------
Rule ID | Title | Notes CONCELIER0002 | Usage | Warning | Legacy merge service usage detected - flags references to `AdvisoryMergeService` and `AddMergeModule`
--------|-------|------
CONCELIER0002 | Legacy merge service usage detected | Flags references to `AdvisoryMergeService` and `AddMergeModule`.

View File

@@ -185,6 +185,11 @@ public sealed class AstraConnector : IFeedConnector
/// </remarks> /// </remarks>
private async Task<string> FetchOvalDatabaseAsync(string version, CancellationToken cancellationToken) private async Task<string> FetchOvalDatabaseAsync(string version, CancellationToken cancellationToken)
{ {
if (_fetchService is null || _rawDocumentStorage is null)
{
throw new InvalidOperationException("Fetch and raw document storage services are required for OVAL database fetch");
}
var uri = _options.BuildOvalDatabaseUri(version); var uri = _options.BuildOvalDatabaseUri(version);
_logger.LogDebug("Fetching OVAL database for Astra Linux {Version} from {Uri}", version, uri); _logger.LogDebug("Fetching OVAL database for Astra Linux {Version} from {Uri}", version, uri);
@@ -197,18 +202,24 @@ public sealed class AstraConnector : IFeedConnector
var result = await _fetchService.FetchAsync(request, cancellationToken).ConfigureAwait(false); var result = await _fetchService.FetchAsync(request, cancellationToken).ConfigureAwait(false);
if (!result.IsSuccess || result.Document is null) if (result is null || !result.IsSuccess || result.Document is null)
{ {
throw new InvalidOperationException($"Failed to fetch OVAL database for version {version}"); throw new InvalidOperationException($"Failed to fetch OVAL database for version {version}");
} }
if (!result.Document.PayloadId.HasValue) var document = result.Document;
if (!document.PayloadId.HasValue)
{ {
throw new InvalidOperationException($"OVAL database document for version {version} has no payload"); throw new InvalidOperationException($"OVAL database document for version {version} has no payload");
} }
// Download the raw XML content // Download the raw XML content
var payloadBytes = await _rawDocumentStorage.DownloadAsync(result.Document.PayloadId.Value, cancellationToken).ConfigureAwait(false); var payloadBytes = await _rawDocumentStorage.DownloadAsync(document.PayloadId.Value, cancellationToken).ConfigureAwait(false);
if (payloadBytes is null)
{
throw new InvalidOperationException($"OVAL database payload for version {version} not found");
}
return System.Text.Encoding.UTF8.GetString(payloadBytes); return System.Text.Encoding.UTF8.GetString(payloadBytes);
} }

View File

@@ -359,7 +359,7 @@ public sealed class CccsHtmlParser
var candidate = href.Trim(); var candidate = href.Trim();
var hasAbsolute = Uri.TryCreate(candidate, UriKind.Absolute, out var absolute); var hasAbsolute = Uri.TryCreate(candidate, UriKind.Absolute, out var absolute);
if (!hasAbsolute || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) if (!hasAbsolute || absolute is null || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
{ {
if (baseUri is null || !Uri.TryCreate(baseUri, candidate, out absolute)) if (baseUri is null || !Uri.TryCreate(baseUri, candidate, out absolute))
{ {

View File

@@ -177,7 +177,7 @@ public sealed class CertCcConnector : IFeedConnector
await _documentStore.UpdateStatusAsync(result.Document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false); await _documentStore.UpdateStatusAsync(result.Document.Id, DocumentStatuses.Mapped, cancellationToken).ConfigureAwait(false);
} }
if (!shouldProcessNotes) if (!shouldProcessNotes || result.Document is null)
{ {
continue; continue;
} }

View File

@@ -168,7 +168,7 @@ internal sealed record CertCcCursor(
} }
var bytes = binary.AsByteArray; var bytes = binary.AsByteArray;
if (bytes.Length == 16) if (bytes is not null && bytes.Length == 16)
{ {
guid = new Guid(bytes); guid = new Guid(bytes);
return true; return true;

View File

@@ -605,7 +605,7 @@ public sealed class DebianConnector : IFeedConnector
{ {
["advisoryId"] = dto.AdvisoryId, ["advisoryId"] = dto.AdvisoryId,
["sourcePackage"] = dto.SourcePackage, ["sourcePackage"] = dto.SourcePackage,
["title"] = dto.Title, ["title"] = dto.Title ?? string.Empty,
["description"] = dto.Description ?? string.Empty, ["description"] = dto.Description ?? string.Empty,
["cves"] = new DocumentArray(dto.CveIds), ["cves"] = new DocumentArray(dto.CveIds),
["packages"] = packages, ["packages"] = packages,

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