diff --git a/.github/workflows/pr-green-api.yml b/.github/workflows/pr-green-api.yml index 28a113f..5e91c23 100644 --- a/.github/workflows/pr-green-api.yml +++ b/.github/workflows/pr-green-api.yml @@ -23,7 +23,7 @@ on: appname: description: "Override the application name in reports (default: repo name)" required: false - default: "" + default: "oprtimapi" bearer_token: description: >- Optional Bearer token for authenticated API endpoints. @@ -68,6 +68,26 @@ on: type: boolean required: false default: false + consumer_region: + description: >- + ISO-3166 alpha-2 region of the API consumers (e.g. FR, US, DE). + Drives AR02 (runtime close to consumer) distance scoring. + required: false + default: "FR" + enable_geoip: + description: >- + Enable optional GeoIP lookup (ipinfo.io) to compute API-vs-consumer + location and anycast/ASN cross-validation for AR02. + type: boolean + required: false + default: true + cloud_footprint_confirmed: + description: >- + Confirm that the cloud provider's carbon footprint dashboard is + actively used (required to validate AR05). + type: boolean + required: false + default: true env: JAVA_VERSION: '21' @@ -264,6 +284,10 @@ jobs: DEBUG: ${{ github.event.inputs.debug || 'false' }} # Bearer: prefer secret, fallback to manual input (logs may show value). BEARER_TOKEN: ${{ secrets.BEARER_TOKEN || github.event.inputs.bearer_token }} + # Architecture rules (AR02 Phase 3 + AR05) β€” wired to green-api-auto-discover.py + CONSUMER_REGION: ${{ github.event.inputs.consumer_region || '' }} + ENABLE_GEOIP: ${{ github.event.inputs.enable_geoip || 'false' }} + CLOUD_FOOTPRINT_CONFIRMED: ${{ github.event.inputs.cloud_footprint_confirmed || 'false' }} steps: - uses: actions/checkout@v4 - name: Setup JDK ${{ env.JAVA_VERSION }} @@ -403,6 +427,19 @@ jobs: if [ "${DEBUG:-false}" = "true" ]; then ARGS+=(--debug) fi + # Architecture rules (AR02 Phase 3 + AR04 Phase 2 + AR05) + # SOURCE_DIR points to the checked-out repo root so AR04 can scan IaC + # and AR01 can cross-validate broker dependencies. + ARGS+=(--source-dir "$GITHUB_WORKSPACE") + if [ -n "${CONSUMER_REGION:-}" ]; then + ARGS+=(--consumer-region "$CONSUMER_REGION") + fi + if [ "${ENABLE_GEOIP:-false}" = "true" ]; then + ARGS+=(--enable-geoip) + fi + if [ "${CLOUD_FOOTPRINT_CONFIRMED:-false}" = "true" ]; then + ARGS+=(--cloud-footprint-confirmed) + fi echo "🌿 Analyzing ${#T_ARR[@]} target(s): $TARGETS (appname=$APPNAME)" python3 scripts/green-api-auto-discover.py \ "${ARGS[@]}" \ @@ -736,6 +773,10 @@ jobs: APPNAME: ${{ github.event.inputs.appname || 'appoptim' }} DEBUG: ${{ github.event.inputs.debug || 'false' }} BEARER_TOKEN: ${{ secrets.BEARER_TOKEN || github.event.inputs.bearer_token }} + # Architecture rules (AR02 / AR04 / AR05) + CONSUMER_REGION: ${{ github.event.inputs.consumer_region || '' }} + ENABLE_GEOIP: ${{ github.event.inputs.enable_geoip || 'false' }} + CLOUD_FOOTPRINT_CONFIRMED: ${{ github.event.inputs.cloud_footprint_confirmed || 'false' }} steps: - uses: actions/checkout@v4 - name: Setup JDK ${{ env.JAVA_VERSION }} @@ -809,6 +850,17 @@ jobs: if [ "${DEBUG:-false}" = "true" ]; then ARGS+=(--debug) fi + # Architecture rules (AR02 / AR04 / AR05) + ARGS+=(--source-dir "$GITHUB_WORKSPACE") + if [ -n "${CONSUMER_REGION:-}" ]; then + ARGS+=(--consumer-region "$CONSUMER_REGION") + fi + if [ "${ENABLE_GEOIP:-false}" = "true" ]; then + ARGS+=(--enable-geoip) + fi + if [ "${CLOUD_FOOTPRINT_CONFIRMED:-false}" = "true" ]; then + ARGS+=(--cloud-footprint-confirmed) + fi python3 scripts/green-api-auto-discover.py \ "${ARGS[@]}" \ --repeat 3 \ diff --git a/.spectral.yml b/.spectral.yml index 7ba756b..4a308f4 100644 --- a/.spectral.yml +++ b/.spectral.yml @@ -1,79 +1,371 @@ extends: [[spectral:oas, recommended]] +# ========================================================================= +# Green API Score - Spectral Rules (static OpenAPI lint) +# +# Ce fichier est COMPLEMENTAIRE au runtime analyzer +# (scripts/green-api-auto-discover.py). Les regles ci-dessous ne valident +# que ce qui est observable statiquement dans la spec OpenAPI. +# +# Les preuves dynamiques (compression reelle, ETag/304 effectif, /changes +# qui repond, runtime regionalement proche, scalabilite, dashboard cloud) +# restent a la charge du runtime analyzer. +# +# Convention: la `description` de chaque regle debute par +# [] - +# ou in { data-efficiency, usage, logs-observability, +# architecture, infrastructure, style } +# pour rester aligne avec le champ `category` du dictionnaire +# GREEN_RULES cote script Python (green-api-auto-discover.py). +# ========================================================================= + +aliases: + GET_OPERATIONS: + - "$.paths[*].get" + ALL_OPERATIONS: + - "$.paths[*][get,post,put,patch,delete]" + COLLECTION_GETS: + # paths whose final segment is NOT a path parameter (= collections) + - "$.paths[?(!@property.match(/\\{[^}]+\\}\\s*$/))].get" + ITEM_GETS: + # paths whose final segment IS a path parameter (= ressource unitaire) + - "$.paths[?(@property.match(/\\{[^}]+\\}\\s*$/))].get" + rules: - # ═══════════════════════════════════════════════════════════ - # 🌿 Green API Score β€” Spectral Rules - # RΓ¨gles de linting OpenAPI pour l'Γ©co-conception d'API - # ═══════════════════════════════════════════════════════════ + # ----------------------------------------------------------------------- + # DATA EFFICIENCY (DE) + # ----------------------------------------------------------------------- - # DE11 β€” Pagination obligatoire sur les collections + # DE11 - Pagination obligatoire sur les GET de collection green-api-pagination: - description: "DE11 β€” Les endpoints de collection GET doivent supporter la pagination (page/size ou limit/offset)" + description: "[data-efficiency] DE11 - Les GET de collection doivent supporter la pagination (page/size, limit/offset, cursor)." severity: warn - given: "$.paths[*].get" + given: "#COLLECTION_GETS" then: - - field: parameters - function: truthy - message: "{{description}} β€” Ajoutez des paramΓ¨tres page/size sur les GET de collection." + function: schema + functionOptions: + schema: + type: object + required: [parameters] + properties: + parameters: + type: array + contains: + type: object + required: [name] + properties: + name: + type: string + pattern: "^(page|size|limit|offset|pageSize|pageNumber|cursor|after|before)$" + message: "{{description}} Aucun parametre de pagination detecte sur ce GET de collection." - # DE08 β€” Filtrage de champs + # DE08 - Filtrage / projection de champs green-api-fields-filter: - description: "DE08 β€” Les endpoints GET devraient supporter un paramΓ¨tre 'fields' pour le filtrage de champs" + description: "[data-efficiency] DE08 - Les GET devraient exposer un parametre de projection (fields, select, _fields, view)." severity: info - given: "$.paths[*].get.parameters[*]" + given: "#GET_OPERATIONS" then: - field: name - function: pattern + function: schema functionOptions: - notMatch: "^$" - message: "Pensez Γ  ajouter un paramΓ¨tre 'fields' pour rΓ©duire le payload (DE08)." + schema: + type: object + required: [parameters] + properties: + parameters: + type: array + contains: + type: object + required: [name] + properties: + name: + type: string + pattern: "^(fields|select|_fields|sparse|view)$" + message: "{{description}}" - # DE01 β€” Format de rΓ©ponse + # DE01 - Format de reponse JSON (et tolerance CBOR/protobuf) green-api-response-format: - description: "DE01 β€” Les rΓ©ponses doivent utiliser application/json (ou application/cbor)" + description: "[data-efficiency] DE01 - Les reponses 2xx doivent exposer application/json (application/cbor / application/x-protobuf toleres en complement)." + severity: warn + given: "$.paths[*][*].responses[?(@property.match(/^2\\d\\d$/))].content" + then: + function: schema + functionOptions: + schema: + type: object + anyOf: + - required: ["application/json"] + - required: ["application/cbor"] + - required: ["application/x-protobuf"] + - required: ["application/protobuf"] + message: "{{description}}" + + # DE01 - Compression : header Content-Encoding documente sur les 200 + green-api-compression-header: + description: "[data-efficiency] DE01 - Documentez le header Content-Encoding (gzip/br) ou Vary sur les reponses 200 pour signaler la compression." + severity: info + given: "$.paths[*][*].responses.200" + then: + function: schema + functionOptions: + schema: + type: object + required: [headers] + properties: + headers: + type: object + propertyNames: + pattern: "(?i)^(content-encoding|vary)$" + message: "{{description}}" + + # DE02/DE03 - Cache : ETag sur les ressources unitaires + green-api-etag-header: + description: "[data-efficiency] DE02/DE03 - Les GET unitaires (/.../{id}) doivent documenter le header ETag dans la reponse 200." severity: warn + given: "#ITEM_GETS" + then: + function: schema + functionOptions: + schema: + type: object + required: [responses] + properties: + responses: + type: object + required: ["200"] + properties: + "200": + type: object + required: [headers] + properties: + headers: + type: object + anyOf: + - required: [ETag] + - required: [etag] + message: "{{description}}" + + # DE02/DE03 - Reponse 304 Not Modified pour les ressources unitaires + green-api-not-modified-304: + description: "[data-efficiency] DE02/DE03 - Les GET unitaires (/.../{id}) doivent declarer la reponse 304 Not Modified." + severity: info + given: "#ITEM_GETS" + then: + field: "responses.304" + function: truthy + message: "{{description}}" + + # DE06 - Delta / Changes (presence d'un endpoint de synchronisation incrementale) + green-api-delta-endpoint: + description: "[data-efficiency] DE06 - La spec devrait exposer un endpoint de delta (/changes, /sync, /delta)." + severity: info + given: "$" + then: + field: paths + function: schema + functionOptions: + schema: + type: object + # "il existe au moins une path-key contenant changes / sync / delta" + # encode via: not(toutes les cles ne matchent pas) + not: + propertyNames: + not: + pattern: "(?i)(changes|/sync(\\b|$|/)|delta)" + message: "{{description}}" + + # 206 - Range / Partial Content : si un endpoint accepte 'Range', il doit declarer 206 + green-api-range-206: + description: "[data-efficiency] 206 - Si l'operation accepte un header Range, elle doit declarer la reponse 206 Partial Content." + severity: info + given: "$.paths[*][get,head][?(@.parameters && @.parameters[?(@.name && @.name.match(/^range$/i))])]" + then: + field: "responses.206" + function: truthy + message: "{{description}}" + + # BIN01 - Format binaire (CBOR / protobuf) - encouragement non bloquant + green-api-binary-format: + description: "[data-efficiency] BIN01 - Pensez a exposer au moins un endpoint en format binaire (application/cbor, application/x-protobuf) pour reduire le payload." + severity: hint given: "$.paths[*][*].responses[*].content" then: - field: "application/json" + function: schema + functionOptions: + schema: + type: object + anyOf: + - required: ["application/cbor"] + - required: ["application/x-protobuf"] + - required: ["application/protobuf"] + - required: ["application/octet-stream"] + - required: ["application/x-ndjson"] + - required: ["text/event-stream"] + # tolere les schemas JSON pour ne pas spammer (regle indicative) + - required: ["application/json"] + message: "{{description}}" + + # ----------------------------------------------------------------------- + # USAGE (US) + # ----------------------------------------------------------------------- + + # US07 - Rate limiting : reponse 429 documentee + green-api-rate-limit-429: + description: "[usage] US07 - Les operations doivent documenter la reponse 429 Too Many Requests." + severity: info + given: "#ALL_OPERATIONS" + then: + field: "responses.429" function: truthy message: "{{description}}" - # DE02/DE03 β€” Cache headers - green-api-cache-headers: - description: "DE02/DE03 β€” Les rΓ©ponses 200 devraient documenter les headers de cache (ETag, Cache-Control)" + # US07 - Rate limiting : header Retry-After / X-RateLimit-* sur la 429 + green-api-retry-after-header: + description: "[usage] US07 - La reponse 429 doit documenter au moins un header Retry-After ou X-RateLimit-*." severity: info - given: "$.paths[*].get.responses.200.headers" + given: "$.paths[*][*].responses.429" then: + function: schema + functionOptions: + schema: + type: object + required: [headers] + properties: + headers: + type: object + propertyNames: + pattern: "(?i)^retry-after$|^x-ratelimit-(limit|remaining|reset)$" + message: "{{description}}" + + # US07 - 404 documentee sur les operations qui ciblent une ressource + green-api-error-responses: + description: "[usage] US07 - Documentez la reponse 404 sur les operations qui ciblent une ressource." + severity: info + given: "$.paths[*][get,put,patch,delete].responses" + then: + field: "404" function: truthy - message: "Documentez les headers ETag et Cache-Control dans vos rΓ©ponses 200 (DE02/DE03)." + message: "{{description}}" + + # ----------------------------------------------------------------------- + # LOGS & OBSERVABILITY (LO) + # ----------------------------------------------------------------------- + + # LO01 - Observabilite : la spec devrait exposer health/metrics/actuator + green-api-observability-endpoint: + description: "[logs-observability] LO01 - La spec devrait exposer un endpoint d'observabilite (/health, /actuator, /metrics, /readiness, /liveness)." + severity: info + given: "$" + then: + field: paths + function: schema + functionOptions: + schema: + type: object + not: + propertyNames: + not: + pattern: "(?i)(health|actuator|metrics|readiness|liveness|ping)" + message: "{{description}}" + + # ----------------------------------------------------------------------- + # ARCHITECTURE (AR) + # ----------------------------------------------------------------------- + + # AR01 - Architecture evenementielle : callbacks OpenAPI, webhooks ou SSE + green-api-event-driven-callbacks: + description: "[architecture] AR01 - Preferez une architecture evenementielle (callbacks OpenAPI, webhooks, text/event-stream) plutot que du polling." + severity: info + given: "$" + then: + function: schema + functionOptions: + schema: + type: object + anyOf: + # callbacks OpenAPI 3 dans components + - properties: + components: + type: object + required: [callbacks] + required: [components] + # webhooks OpenAPI 3.1 + - required: [webhooks] + # SSE: presence d'un endpoint stream/events/sse/subscribe/notifications + - properties: + paths: + type: object + not: + propertyNames: + not: + pattern: "(?i)(stream|events|sse|subscribe|notifications?)" + required: [paths] + message: "{{description}}" - # Pas de descriptions vides + # ----------------------------------------------------------------------- + # STYLE / DOCUMENTATION (transverse) + # ----------------------------------------------------------------------- + + # Toute operation doit avoir une description green-api-operation-description: - description: "Chaque opΓ©ration doit avoir une description" + description: "[style] Chaque operation doit avoir une description (documentation)." severity: warn - given: "$.paths[*][get,post,put,patch,delete]" + given: "#ALL_OPERATIONS" then: field: description function: truthy - message: "Ajoutez une description Γ  cette opΓ©ration pour la documentation." + message: "{{description}}" - # RΓ©ponses d'erreur documentΓ©es - green-api-error-responses: - description: "US07 β€” Documentez les rΓ©ponses d'erreur (400, 404, 500)" + # Toute operation doit avoir un operationId stable + green-api-operation-id: + description: "[style] Chaque operation doit avoir un operationId stable (generation de clients, tracabilite)." + severity: warn + given: "#ALL_OPERATIONS" + then: + field: operationId + function: truthy + message: "{{description}}" + + # Toute operation doit avoir au moins une reponse 2xx + green-api-success-2xx: + description: "[style] Chaque operation doit declarer au moins une reponse 2xx." + severity: warn + given: "#ALL_OPERATIONS" + then: + field: responses + function: schema + functionOptions: + schema: + type: object + anyOf: + - required: ["200"] + - required: ["201"] + - required: ["202"] + - required: ["204"] + - required: ["2XX"] + message: "{{description}}" + + # DELETE devrait retourner 204 (pas de payload de retour) + green-api-delete-204: + description: "[data-efficiency] Les operations DELETE devraient retourner 204 No Content (pas de payload de retour)." severity: info - given: "$.paths[*][*].responses" + given: "$.paths[*].delete" then: - field: "404" + field: "responses.204" function: truthy - message: "Documentez la rΓ©ponse 404 pour amΓ©liorer l'observabilitΓ© (US07)." + message: "{{description}}" + + # ----------------------------------------------------------------------- + # Surcharges des regles spectral:oas + # ----------------------------------------------------------------------- - # Pas de payload excessif dans les exemples oas3-valid-media-example: severity: warn - - # Info obligatoire info-contact: severity: warn info-description: severity: warn + operation-tag-defined: + severity: info + no-$ref-siblings: + severity: warn diff --git a/badges/green-score.svg b/badges/green-score.svg index bc30f72..7f5986a 100644 --- a/badges/green-score.svg +++ b/badges/green-score.svg @@ -1,5 +1,5 @@ - - Green Score: 34/100 (D) + + Green Score: 39/100 (D) @@ -9,6 +9,6 @@ Green Score - 34/100 (D) + 39/100 (D) diff --git a/dashboard/index.html b/dashboard/index.html index 882ac4c..c4bd7c4 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -1189,8 +1189,8 @@

πŸŽ›οΈ Actions