diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 105b9ce..e10f96b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,14 +3,8 @@ # Runs on push to develop and on PRs targeting develop. # # Jobs: -# 1. quality — Install, lint, test & verify (tox), SonarCloud -# 2. trigger-staging-deploy — on push to develop only, triggers the -# Deploy ERSys Staging workflow on enity-resolution-ops via the -# GitHub workflow_dispatch API -# -# Required secrets: -# - SONAR_TOKEN: SonarCloud authentication token -# - CI_GH_TOKEN: org-level PAT for cross-repo workflow dispatch +# 1. quality — Install, lint, test & verify (tox) + name: CI @@ -45,7 +39,7 @@ jobs: # ------------------------------------------------------------------ - uses: actions/checkout@v6 with: - fetch-depth: 0 # Full history required for SonarCloud analysis + fetch-depth: 0 # ------------------------------------------------------------------ # Python & Poetry @@ -99,39 +93,3 @@ jobs: REDIS_HOST: localhost REDIS_PORT: 6379 REDIS_PASSWORD: "" - - # ------------------------------------------------------------------ - # SonarCloud - # ------------------------------------------------------------------ - - name: Check for SonarCloud Token - id: sonar_check - run: | - if [ -z "${{ secrets.SONAR_TOKEN }}" ]; then - echo "has_token=false" >> $GITHUB_OUTPUT - else - echo "has_token=true" >> $GITHUB_OUTPUT - fi - - - name: SonarCloud scan - if: always() && steps.sonar_check.outputs.has_token == 'true' - uses: SonarSource/sonarqube-scan-action@v6 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - trigger-staging-deploy: - name: Trigger staging deploy - needs: quality - if: github.event_name == 'push' && github.repository_owner == 'meaningfy-ws' - runs-on: ubuntu-latest - env: - OPS_REPO: meaningfy-ws/enity-resolution-ops - DEPLOY_WORKFLOW: deploy-staging.yml - DEPLOY_REF: develop - steps: - - name: Trigger deploy workflow on ops repo - run: | - curl -sf -X POST \ - -H "Authorization: token ${{ secrets.CI_GH_TOKEN }}" \ - -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${OPS_REPO}/actions/workflows/${DEPLOY_WORKFLOW}/dispatches" \ - -d '{"ref":"${{ env.DEPLOY_REF }}","inputs":{"repo":"${{ github.repository }}","sha":"${{ github.sha }}"}}' diff --git a/CHANGELOG.md b/CHANGELOG.md index aad6ffa..992fbb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.1.0-rc.6] - 2026-07-16 + +### Changed +* Disable logging of error traceback for controlled exceptions; the traceback logging is retained for uncontrolled exceptions (TEDSWS-529, TEDSWS-534) + +### Removed +* SonarCloud integration, as it depended on contractor-specific configuration (TEDSWS-528). + + +## [1.0.0-rc.2] - 2026-06-30 + +### Changed +* ERSys installation instructions and related documentation improved (TEDSWS-520) +* Contractor-specific references removed from the source code repositories (TEDSWS-528) + + ## [1.1.0-rc.4] - 2026-06-30 ### Changed diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 0a21d94..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,140 +0,0 @@ -# SonarCloud Configuration for Entity Resolution Engine (ERE) -# =========================================================== -# This file configures quality gates and analysis for SonarCloud. -# See: https://docs.sonarcloud.io/ - -#----------------------------------------------------------------------------- -# Project Identity -#----------------------------------------------------------------------------- - -# Must be unique in SonarCloud instance -sonar.projectKey=meaningfy-ws_entity-resolution-engine-basic -sonar.organization=meaningfy-ws - -# Display name and version -sonar.projectName=Entity Resolution Engine (ERE) -sonar.projectVersion=1.0.0 - -#----------------------------------------------------------------------------- -# Code Analysis Paths -#----------------------------------------------------------------------------- - -# Source code location (relative to sonar-project.properties) -sonar.sources=src/ere -sonar.tests=test - -# Source encoding -sonar.sourceEncoding=UTF-8 - -# Python version (must match pyproject.toml requires-python) -sonar.python.version=3.12 - -#----------------------------------------------------------------------------- -# Coverage & Test Report Paths -#----------------------------------------------------------------------------- - -# Coverage report (generated by pytest-cov via tox) -sonar.python.coverage.reportPaths=coverage.xml - -# Test results report (if using XML format) -sonar.python.xunit.reportPath=test-results.xml - -# Pylint report (optional, for additional insights) -sonar.python.pylint.reportPath=pylint-report.txt - -#----------------------------------------------------------------------------- -# Exclusions (don't analyze these) -#----------------------------------------------------------------------------- - -# Exclude test files from coverage metrics -sonar.coverage.exclusions=test/**/*,setup.py,**/__init__.py - -# Exclude test files from duplication detection -sonar.cpd.exclusions=test/**/* - -# Exclude documentation and config files from analysis -sonar.exclusions=docs/**/*,*.md,src/infra/**/*,src/demo/**/* - -#----------------------------------------------------------------------------- -# SOLID Principles & Clean Code Quality Gates -#----------------------------------------------------------------------------- - -# Single Responsibility Principle (SRP) -# Cognitive complexity per function (max 15 is recommended) -sonar.python.S3776.threshold=15 - -# Cyclomatic complexity per function (max 10 is good practice) -sonar.python.S1541.threshold=10 - -# Max lines per function (enforce small, focused functions) -sonar.python.S104.max=50 - -# Max lines per class (enforce focused classes) -sonar.python.S1188.max=500 - -# Open/Closed Principle (OCP) -# Discourage too many conditional statements (suggests need for polymorphism) -sonar.python.S1066.max=3 - -# Liskov Substitution Principle (LSP) -# Limit inheritance depth to avoid deep hierarchies -sonar.python.S110.max=4 - -# Don't Repeat Yourself (DRY) - Related to SOLID -# Minimum duplicate lines to trigger an issue -sonar.cpd.python.minimumLines=3 - -# Minimum duplicate tokens -sonar.cpd.python.minimumTokens=50 - -#----------------------------------------------------------------------------- -# Quality Gate Configuration -# -# Note: These conditions must also be configured in SonarCloud UI under: -# Organization Settings → Quality Gates → Create "ERE SOLID Gate" -# -# Create custom quality gate with these conditions on New Code: -# - Critical Issues: is greater than 0 → FAIL -# - Blocker Issues: is greater than 0 → FAIL -# - Security Hotspots Reviewed: is less than 100% → FAIL -# - Coverage: is less than 80% → FAIL -# - Duplicated Lines: is greater than 3% → FAIL -# - Code Smells: is greater than 0 → FAIL (optional) -# -#----------------------------------------------------------------------------- - -# Wait for quality gate result (useful in CI) -sonar.qualitygate.wait=true -sonar.qualitygate.timeout=300 - -# Branch analysis configuration -sonar.branch.autoconfig.disabled=false - -#----------------------------------------------------------------------------- -# Maintainability & Code Quality Thresholds -# -# Rating scale: -# A = tech debt ratio <= 5% -# B = tech debt ratio 6-10% -# C = tech debt ratio 11-20% -# -#----------------------------------------------------------------------------- - -# Enforce at least B rating -sonar.maintainability.rating.threshold=B - -# New code should have zero code smells (potential issues) -sonar.newCode.codeSmells=0 - -# Overall complexity threshold for functions -sonar.complexity.threshold=10 - -#----------------------------------------------------------------------------- -# Reporting -#----------------------------------------------------------------------------- - -# Generate reports for historical tracking -sonar.report.export.path=sonar-report - -# Verbose logging (helpful for debugging) -sonar.verbose=false diff --git a/src/VERSION b/src/VERSION index 61cb4de..812896c 100644 --- a/src/VERSION +++ b/src/VERSION @@ -1 +1 @@ -1.1.0-rc.4 +1.1.0-rc.6 diff --git a/src/ere/services/entity_resolution_service.py b/src/ere/services/entity_resolution_service.py index dbc5e36..d5a05cd 100644 --- a/src/ere/services/entity_resolution_service.py +++ b/src/ere/services/entity_resolution_service.py @@ -20,6 +20,7 @@ MentionRepository, SimilarityRepository, ) +from ere.models.exceptions import ConflictError from ere.models.resolver import ( CandidateCluster, ClusterId, @@ -229,8 +230,6 @@ def check_conflict(self, mention: Mention) -> None: Raises: ConflictError: If mention_id already exists with different attributes. """ - from ere.models.exceptions import ConflictError - existing = self._mention_repo.find_by_id(mention.id) if existing is not None and existing.attributes != mention.attributes: raise ConflictError( @@ -488,10 +487,11 @@ def process_request(self, request: ERERequest) -> EREResponse: ere_request_id=request.ere_request_id, timestamp=now, ) - except Exception as exc: # pylint: disable=broad-exception-caught - log.exception( - "Resolution error for mention %s", + except (ValueError, ConflictError) as exc: + log.error( # NOSONAR - intentionally no traceback for controlled exceptions + "Resolution error for mention %s: %s", request.ere_request_id, + exc, ) return EREErrorResponse( ere_request_id=request.ere_request_id, @@ -500,6 +500,18 @@ def process_request(self, request: ERERequest) -> EREResponse: error_detail=str(exc), timestamp=now, ) + except Exception: # pylint: disable=broad-exception-caught + log.exception( + "Unexpected error for mention %s", + request.ere_request_id, + ) + return EREErrorResponse( + ere_request_id=request.ere_request_id, + error_type="InternalError", + error_title="Unexpected error", + error_detail="An unexpected error occurred", + timestamp=now, + ) def __call__(self, request: ERERequest) -> EREResponse: """Make the service callable.""" diff --git a/src/infra/Dockerfile b/src/infra/Dockerfile index cf92f40..7f4ae02 100644 --- a/src/infra/Dockerfile +++ b/src/infra/Dockerfile @@ -69,10 +69,10 @@ RUN groupadd --gid 1000 appuser && \ WORKDIR /app -# Copy only the virtual environment and necessary app files -COPY --from=builder /app/.venv /app/.venv -COPY --from=builder /app/ere /app/ere -COPY --from=builder /app/config /app/config +# Copy only the virtual environment and necessary app files — chown to appuser so non-root process can read +COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv +COPY --from=builder --chown=appuser:appuser /app/ere /app/ere +COPY --from=builder --chown=appuser:appuser /app/config /app/config # Volume mount point for DuckDB persistent storage RUN mkdir -p /data && chown appuser:appuser /data diff --git a/test/resources/rdf_mapping.yaml b/test/resources/rdf_mapping.yaml index 8dd02e2..4b856ed 100644 --- a/test/resources/rdf_mapping.yaml +++ b/test/resources/rdf_mapping.yaml @@ -1,20 +1,20 @@ -# Namespace prefix registry - used by rdf_mapper.py to resolve prefixed names in field paths -namespaces: - epo: "http://data.europa.eu/a4g/ontology#" - org: "http://www.w3.org/ns/org#" - locn: "http://www.w3.org/ns/locn#" - cccev: "http://data.europa.eu/m8g/" - -# Entity type mappings: entity_type_string -> rdf_type + field property paths -# Property paths use / as separator for multi-hop traversal. -# Field names must match entity_fields in resolver.yaml (legal_name, country_code). -entity_types: - ORGANISATION: - rdf_type: "org:Organization" - fields: - legal_name: "epo:hasLegalName" - country_code: "cccev:registeredAddress/epo:hasCountryCode" - nuts_code: "cccev:registeredAddress/epo:hasNutsCode" - post_code: "cccev:registeredAddress/locn:postCode" - post_name: "cccev:registeredAddress/locn:postName" - thoroughfare: "cccev:registeredAddress/locn:thoroughfare" +# Namespace prefix registry - used by rdf_mapper.py to resolve prefixed names in field paths +namespaces: + epo: "http://data.europa.eu/a4g/ontology#" + org: "http://www.w3.org/ns/org#" + locn: "http://www.w3.org/ns/locn#" + cccev: "http://data.europa.eu/m8g/" + +# Entity type mappings: entity_type_string -> rdf_type + field property paths +# Property paths use / as separator for multi-hop traversal. +# Field names must match entity_fields in resolver.yaml (legal_name, country_code). +entity_types: + ORGANISATION: + rdf_type: "org:Organization" + fields: + legal_name: "epo:hasLegalName" + country_code: "cccev:registeredAddress/epo:hasCountryCode" + nuts_code: "cccev:registeredAddress/epo:hasNutsCode" + post_code: "cccev:registeredAddress/locn:postCode" + post_name: "cccev:registeredAddress/locn:postName" + thoroughfare: "cccev:registeredAddress/locn:thoroughfare"