From 1e82df67401e25eade00398b89377df303390457 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Thu, 6 Aug 2026 09:35:40 +0200 Subject: [PATCH 1/4] fix(openapi): preserve SDK contract semantics --- .speakeasy/workflow.lock | 6 +- Justfile | 10 +++ releases/overlays/shared.overlay.yaml | 97 ++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 98b6a43df4..4a96dac629 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,9 +1,9 @@ -speakeasyVersion: 1.791.1 +speakeasyVersion: 1.791.4 sources: stacks-source: sourceNamespace: stacks-source - sourceRevisionDigest: sha256:6c3c89b43c0d8a0f2fecf34ff86937fc61d691088dc23ac99c12b455daefb396 - sourceBlobDigest: sha256:13724b4f3a2771b9912c10beaaecd2e73b3ca8203d7f7b40cadaca7014f45994 + sourceRevisionDigest: sha256:cd7b762a74f4f135c22b7ed63c429980a185a75751f8b2925968c84691ea906d + sourceBlobDigest: sha256:c8258b5d360aa032cdb76b11afdd41303785ffd87c076b4d9c30bee69472add2 tags: - latest - SDK_VERSION diff --git a/Justfile b/Justfile index c583cce2fd..2519b39107 100644 --- a/Justfile +++ b/Justfile @@ -42,6 +42,16 @@ build-openapi version="v0.0.0": strip-servers mkdir -p releases/build speakeasy run -s all cd releases && sed -i'' -e 's/SDK_VERSION/{{ version }}/g' build/generate.json + just validate-openapi + +# Validate invariants introduced by composing namespaced OpenAPI documents. +validate-openapi: + # Every local discriminator mapping must resolve to an existing schema. + jq -e '.components.schemas as $schemas | [ $schemas | to_entries[] | select(.value.discriminator.mapping? != null) | .value.discriminator.mapping[] | select(startswith("#/components/schemas/")) | sub("^#/components/schemas/"; "") | select($schemas[.] == null) ] | length == 0' releases/build/generate.json >/dev/null + # Ledger query-template helpers rely on these resource constants. + test "$(jq -c '[.components.schemas.ledger_V2QueryParams.oneOf[].properties.resource.const]' releases/build/generate.json)" = '["accounts","transactions","logs","volumes"]' + # Error responses must not be generated as successful response variants. + test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["4XX","5XX","default"]' # Generate event schemas generate-events: diff --git a/releases/overlays/shared.overlay.yaml b/releases/overlays/shared.overlay.yaml index 467dfeba6f..c98519b692 100644 --- a/releases/overlays/shared.overlay.yaml +++ b/releases/overlays/shared.overlay.yaml @@ -40,9 +40,104 @@ actions: update: x-speakeasy-model-namespace: ledger - # Speakeasy error handling + # Restore discriminator mappings after modelNamespace prefixes schema names. + # Speakeasy namespaces oneOf refs during merge, but discriminator mapping refs + # remain unchanged unless the composite overlay rewrites them explicitly. + - target: $.components.schemas.ledger_V2BulkElement.discriminator.mapping + update: + CREATE_TRANSACTION: '#/components/schemas/ledger_V2BulkElementCreateTransaction' + ADD_METADATA: '#/components/schemas/ledger_V2BulkElementAddMetadata' + REVERT_TRANSACTION: '#/components/schemas/ledger_V2BulkElementRevertTransaction' + DELETE_METADATA: '#/components/schemas/ledger_V2BulkElementDeleteMetadata' + - target: $.components.schemas.ledger_V2BulkElementResult.discriminator.mapping + update: + CREATE_TRANSACTION: '#/components/schemas/ledger_V2BulkElementResultCreateTransaction' + ADD_METADATA: '#/components/schemas/ledger_V2BulkElementResultAddMetadata' + REVERT_TRANSACTION: '#/components/schemas/ledger_V2BulkElementResultRevertTransaction' + DELETE_METADATA: '#/components/schemas/ledger_V2BulkElementResultDeleteMetadata' + ERROR: '#/components/schemas/ledger_V2BulkElementResultError' + - target: $.components.schemas.payments_ConnectorConfig.discriminator.mapping + update: + Stripe: '#/components/schemas/payments_StripeConfig' + Dummypay: '#/components/schemas/payments_DummyPayConfig' + Wise: '#/components/schemas/payments_WiseConfig' + Modulr: '#/components/schemas/payments_ModulrConfig' + Currencycloud: '#/components/schemas/payments_CurrencyCloudConfig' + Bankingcircle: '#/components/schemas/payments_BankingCircleConfig' + Mangopay: '#/components/schemas/payments_MangoPayConfig' + Moneycorp: '#/components/schemas/payments_MoneycorpConfig' + Atlar: '#/components/schemas/payments_AtlarConfig' + Adyen: '#/components/schemas/payments_AdyenConfig' + Generic: '#/components/schemas/payments_GenericConfig' + - target: $.components.schemas.payments_V3ConnectorConfig.discriminator.mapping + update: + Adyen: '#/components/schemas/payments_V3AdyenConfig' + Atlar: '#/components/schemas/payments_V3AtlarConfig' + Bankingbridge: '#/components/schemas/payments_V3BankingbridgeConfig' + Bankingcircle: '#/components/schemas/payments_V3BankingcircleConfig' + Bitstamp: '#/components/schemas/payments_V3BitstampConfig' + Coinbaseprime: '#/components/schemas/payments_V3CoinbaseprimeConfig' + Column: '#/components/schemas/payments_V3ColumnConfig' + Currencycloud: '#/components/schemas/payments_V3CurrencycloudConfig' + Dummypay: '#/components/schemas/payments_V3DummypayConfig' + Fireblocks: '#/components/schemas/payments_V3FireblocksConfig' + Generic: '#/components/schemas/payments_V3GenericConfig' + Increase: '#/components/schemas/payments_V3IncreaseConfig' + Krakenpro: '#/components/schemas/payments_V3KrakenproConfig' + Mangopay: '#/components/schemas/payments_V3MangopayConfig' + Modulr: '#/components/schemas/payments_V3ModulrConfig' + Moneycorp: '#/components/schemas/payments_V3MoneycorpConfig' + Plaid: '#/components/schemas/payments_V3PlaidConfig' + Powens: '#/components/schemas/payments_V3PowensConfig' + Qonto: '#/components/schemas/payments_V3QontoConfig' + Routable: '#/components/schemas/payments_V3RoutableConfig' + Stripe: '#/components/schemas/payments_V3StripeConfig' + Tink: '#/components/schemas/payments_V3TinkConfig' + Wise: '#/components/schemas/payments_V3WiseConfig' + - target: $.components.schemas.wallets_Subject.discriminator.mapping + update: + ACCOUNT: '#/components/schemas/wallets_LedgerAccountSubject' + WALLET: '#/components/schemas/wallets_WalletSubject' + - target: $.components.schemas.orchestration_Subject.discriminator.mapping + update: + ACCOUNT: '#/components/schemas/orchestration_LedgerAccountSubject' + WALLET: '#/components/schemas/orchestration_WalletSubject' + - target: $.components.schemas.orchestration_V2Subject.discriminator.mapping + update: + ACCOUNT: '#/components/schemas/orchestration_V2LedgerAccountSubject' + WALLET: '#/components/schemas/orchestration_V2WalletSubject' + + # Encode Ledger query-template resources as constants so generated union + # helpers initialize the resource tag without discriminator inference. + - target: $.components.schemas.ledger_V2QueryParams.oneOf[0].properties.resource.enum + remove: true + - target: $.components.schemas.ledger_V2QueryParams.oneOf[0].properties.resource + update: + const: accounts + - target: $.components.schemas.ledger_V2QueryParams.oneOf[1].properties.resource.enum + remove: true + - target: $.components.schemas.ledger_V2QueryParams.oneOf[1].properties.resource + update: + const: transactions + - target: $.components.schemas.ledger_V2QueryParams.oneOf[2].properties.resource.enum + remove: true + - target: $.components.schemas.ledger_V2QueryParams.oneOf[2].properties.resource + update: + const: logs + - target: $.components.schemas.ledger_V2QueryParams.oneOf[3].properties.resource.enum + remove: true + - target: $.components.schemas.ledger_V2QueryParams.oneOf[3].properties.resource + update: + const: volumes + + # Treat all HTTP error responses as errors. Component specs contribute their + # own defaults during merge, so remove the merged value before replacing it. + - target: $.paths.x-speakeasy-errors + remove: true - target: $.paths update: x-speakeasy-errors: statusCodes: + - 4XX + - 5XX - default From f84e2d0ed648b7c6a14e78571e0de6cc70a85dd2 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Thu, 6 Aug 2026 09:50:26 +0200 Subject: [PATCH 2/4] refactor(openapi): generate composition overlay --- .../scripts/generate-composition-overlay.sh | 113 +++++++++++++++ .speakeasy/workflow.lock | 5 +- .speakeasy/workflow.yaml | 1 + Justfile | 13 +- releases/overlays/generated.overlay.json | 133 ++++++++++++++++++ releases/overlays/shared.overlay.yaml | 90 ------------ 6 files changed, 259 insertions(+), 96 deletions(-) create mode 100644 .github/scripts/generate-composition-overlay.sh create mode 100644 releases/overlays/generated.overlay.json diff --git a/.github/scripts/generate-composition-overlay.sh b/.github/scripts/generate-composition-overlay.sh new file mode 100644 index 0000000000..ff66f2c53f --- /dev/null +++ b/.github/scripts/generate-composition-overlay.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -euo pipefail + +workflow_file="${1:-.speakeasy/workflow.yaml}" +output_file="${2:-releases/overlays/generated.overlay.json}" + +mkdir -p "$(dirname "$output_file")" + +{ + while IFS=$'\t' read -r location namespace; do + # Speakeasy applies modelNamespace to schema names and structural refs, + # but explicit discriminator mappings need the same transformation. + yq -o=json "$location" | jq -c --arg namespace "$namespace" ' + def json_path_segment: + if type == "number" + then "[" + tostring + "]" + else "[" + (@json) + "]" + end; + + .components.schemas as $schemas + | $schemas + | to_entries[] + | .key as $schema_name + | .value as $schema + | ($schema | path(.. | objects | select(.discriminator.mapping? != null))) as $path + | ($schema | getpath($path) | .discriminator.mapping) as $mapping + | { + target: ( + "$.components.schemas[" + + (($namespace + "_" + $schema_name) | @json) + + "]" + + ($path | map(json_path_segment) | join("")) + + ".discriminator.mapping" + ), + update: ( + $mapping + | with_entries( + .value |= ( + . as $ref + | if ( + ($ref | startswith("#/components/schemas/")) + and ($schemas[$ref | sub("^#/components/schemas/"; "")] != null) + ) + then ( + "#/components/schemas/" + + $namespace + + "_" + + ($ref | sub("^#/components/schemas/"; "")) + ) + elif $schemas[$ref] != null + then "#/components/schemas/" + $namespace + "_" + $ref + else $ref + end + ) + ) + ) + } + ' + + # Ledger uses a tagged inline union while discriminator inference is + # disabled. Convert every singleton resource enum, including future + # branches, into the constant expected by generated union helpers. + if [[ "$namespace" == "ledger" ]]; then + yq -o=json "$location" | jq -c --arg namespace "$namespace" ' + .components.schemas.V2QueryParams.oneOf + | to_entries[] + | select( + (.value.properties.resource.enum? | type) == "array" + and (.value.properties.resource.enum | length) == 1 + ) + | .key as $index + | .value.properties.resource.enum[0] as $resource + | { + target: ( + "$.components.schemas[\"" + + $namespace + + "_V2QueryParams\"].oneOf[" + + ($index | tostring) + + "].properties.resource.enum" + ), + remove: true + }, + { + target: ( + "$.components.schemas[\"" + + $namespace + + "_V2QueryParams\"].oneOf[" + + ($index | tostring) + + "].properties.resource" + ), + update: {const: $resource} + } + ' + fi + done < <( + yq -r ' + .sources[].inputs[] + | select(.modelNamespace != null) + | [.location, .modelNamespace] + | @tsv + ' "$workflow_file" + ) +} | jq -s ' + { + overlay: "1.0.0", + info: { + title: "Generated namespace fixes for the Formance Stack OpenAPI spec", + version: "0.0.1" + }, + actions: . + } +' > "$output_file" diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 4a96dac629..96fe4831e8 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -2,8 +2,8 @@ speakeasyVersion: 1.791.4 sources: stacks-source: sourceNamespace: stacks-source - sourceRevisionDigest: sha256:cd7b762a74f4f135c22b7ed63c429980a185a75751f8b2925968c84691ea906d - sourceBlobDigest: sha256:c8258b5d360aa032cdb76b11afdd41303785ffd87c076b4d9c30bee69472add2 + sourceRevisionDigest: sha256:63386e0de2492cf204e4fc939278e627a488687f6f0a26685810f5a8d08a796c + sourceBlobDigest: sha256:7654239aa54ec23ece2a055b35030708e91851bc2e476169a92d94380117e151 tags: - latest - SDK_VERSION @@ -35,6 +35,7 @@ workflow: - location: ./releases/base.yaml overlays: - location: ./releases/overlays/shared.overlay.yaml + - location: ./releases/overlays/generated.overlay.json output: ./releases/build/generate.json registry: location: registry.speakeasyapi.dev/formance/formance/stacks-source diff --git a/.speakeasy/workflow.yaml b/.speakeasy/workflow.yaml index 1be3f38d05..dcb8cecc64 100644 --- a/.speakeasy/workflow.yaml +++ b/.speakeasy/workflow.yaml @@ -24,6 +24,7 @@ sources: - location: ./releases/base.yaml overlays: - location: ./releases/overlays/shared.overlay.yaml + - location: ./releases/overlays/generated.overlay.json output: ./releases/build/generate.json registry: location: registry.speakeasyapi.dev/formance/formance/stacks-source diff --git a/Justfile b/Justfile index 2519b39107..fe9a51b86f 100644 --- a/Justfile +++ b/Justfile @@ -37,8 +37,13 @@ prepend-paths: download-specs strip-servers: prepend-paths for f in components/*.openapi.yaml; do yq -i 'del(.servers)' "$f"; done +# Generate namespace-aware composition fixes from the configured inputs. +generate-composition-overlay: strip-servers + mkdir -p releases/build + bash .github/scripts/generate-composition-overlay.sh + # Build the merged OpenAPI spec using Speakeasy -build-openapi version="v0.0.0": strip-servers +build-openapi version="v0.0.0": generate-composition-overlay mkdir -p releases/build speakeasy run -s all cd releases && sed -i'' -e 's/SDK_VERSION/{{ version }}/g' build/generate.json @@ -47,9 +52,9 @@ build-openapi version="v0.0.0": strip-servers # Validate invariants introduced by composing namespaced OpenAPI documents. validate-openapi: # Every local discriminator mapping must resolve to an existing schema. - jq -e '.components.schemas as $schemas | [ $schemas | to_entries[] | select(.value.discriminator.mapping? != null) | .value.discriminator.mapping[] | select(startswith("#/components/schemas/")) | sub("^#/components/schemas/"; "") | select($schemas[.] == null) ] | length == 0' releases/build/generate.json >/dev/null + jq -e '.components.schemas as $schemas | [ $schemas | .. | objects | select(.discriminator.mapping? != null) | .discriminator.mapping[] | select(type == "string" and startswith("#/components/schemas/")) | sub("^#/components/schemas/"; "") | select($schemas[.] == null) ] | length == 0' releases/build/generate.json >/dev/null # Ledger query-template helpers rely on these resource constants. - test "$(jq -c '[.components.schemas.ledger_V2QueryParams.oneOf[].properties.resource.const]' releases/build/generate.json)" = '["accounts","transactions","logs","volumes"]' + jq -e '[.components.schemas.ledger_V2QueryParams.oneOf[].properties.resource | select(.const == null or .enum != null)] | length == 0' releases/build/generate.json >/dev/null # Error responses must not be generated as successful response variants. test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["4XX","5XX","default"]' @@ -62,7 +67,7 @@ generate-events: build version="v0.0.0": (build-openapi version) generate-events # Publish OpenAPI spec to Speakeasy Registry -publish-speakeasy version: prepend-paths +publish-speakeasy version: generate-composition-overlay speakeasy run -s all --registry-tags {{ version }},LATEST_RELEASE # Pre-commit: build spec and generate events diff --git a/releases/overlays/generated.overlay.json b/releases/overlays/generated.overlay.json new file mode 100644 index 0000000000..6e81bd2d8d --- /dev/null +++ b/releases/overlays/generated.overlay.json @@ -0,0 +1,133 @@ +{ + "overlay": "1.0.0", + "info": { + "title": "Generated namespace fixes for the Formance Stack OpenAPI spec", + "version": "0.0.1" + }, + "actions": [ + { + "target": "$.components.schemas[\"ledger_V2BulkElement\"].discriminator.mapping", + "update": { + "CREATE_TRANSACTION": "#/components/schemas/ledger_V2BulkElementCreateTransaction", + "ADD_METADATA": "#/components/schemas/ledger_V2BulkElementAddMetadata", + "REVERT_TRANSACTION": "#/components/schemas/ledger_V2BulkElementRevertTransaction", + "DELETE_METADATA": "#/components/schemas/ledger_V2BulkElementDeleteMetadata" + } + }, + { + "target": "$.components.schemas[\"ledger_V2BulkElementResult\"].discriminator.mapping", + "update": { + "CREATE_TRANSACTION": "#/components/schemas/ledger_V2BulkElementResultCreateTransaction", + "ADD_METADATA": "#/components/schemas/ledger_V2BulkElementResultAddMetadata", + "REVERT_TRANSACTION": "#/components/schemas/ledger_V2BulkElementResultRevertTransaction", + "DELETE_METADATA": "#/components/schemas/ledger_V2BulkElementResultDeleteMetadata", + "ERROR": "#/components/schemas/ledger_V2BulkElementResultError" + } + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[0].properties.resource.enum", + "remove": true + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[0].properties.resource", + "update": { + "const": "accounts" + } + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[1].properties.resource.enum", + "remove": true + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[1].properties.resource", + "update": { + "const": "transactions" + } + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[2].properties.resource.enum", + "remove": true + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[2].properties.resource", + "update": { + "const": "logs" + } + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[3].properties.resource.enum", + "remove": true + }, + { + "target": "$.components.schemas[\"ledger_V2QueryParams\"].oneOf[3].properties.resource", + "update": { + "const": "volumes" + } + }, + { + "target": "$.components.schemas[\"payments_ConnectorConfig\"].discriminator.mapping", + "update": { + "Stripe": "#/components/schemas/payments_StripeConfig", + "Dummypay": "#/components/schemas/payments_DummyPayConfig", + "Wise": "#/components/schemas/payments_WiseConfig", + "Modulr": "#/components/schemas/payments_ModulrConfig", + "Currencycloud": "#/components/schemas/payments_CurrencyCloudConfig", + "Bankingcircle": "#/components/schemas/payments_BankingCircleConfig", + "Mangopay": "#/components/schemas/payments_MangoPayConfig", + "Moneycorp": "#/components/schemas/payments_MoneycorpConfig", + "Atlar": "#/components/schemas/payments_AtlarConfig", + "Adyen": "#/components/schemas/payments_AdyenConfig", + "Generic": "#/components/schemas/payments_GenericConfig" + } + }, + { + "target": "$.components.schemas[\"payments_V3ConnectorConfig\"].discriminator.mapping", + "update": { + "Adyen": "#/components/schemas/payments_V3AdyenConfig", + "Atlar": "#/components/schemas/payments_V3AtlarConfig", + "Bankingbridge": "#/components/schemas/payments_V3BankingbridgeConfig", + "Bankingcircle": "#/components/schemas/payments_V3BankingcircleConfig", + "Bitstamp": "#/components/schemas/payments_V3BitstampConfig", + "Coinbaseprime": "#/components/schemas/payments_V3CoinbaseprimeConfig", + "Column": "#/components/schemas/payments_V3ColumnConfig", + "Currencycloud": "#/components/schemas/payments_V3CurrencycloudConfig", + "Dummypay": "#/components/schemas/payments_V3DummypayConfig", + "Fireblocks": "#/components/schemas/payments_V3FireblocksConfig", + "Generic": "#/components/schemas/payments_V3GenericConfig", + "Increase": "#/components/schemas/payments_V3IncreaseConfig", + "Krakenpro": "#/components/schemas/payments_V3KrakenproConfig", + "Mangopay": "#/components/schemas/payments_V3MangopayConfig", + "Modulr": "#/components/schemas/payments_V3ModulrConfig", + "Moneycorp": "#/components/schemas/payments_V3MoneycorpConfig", + "Plaid": "#/components/schemas/payments_V3PlaidConfig", + "Powens": "#/components/schemas/payments_V3PowensConfig", + "Qonto": "#/components/schemas/payments_V3QontoConfig", + "Routable": "#/components/schemas/payments_V3RoutableConfig", + "Stripe": "#/components/schemas/payments_V3StripeConfig", + "Tink": "#/components/schemas/payments_V3TinkConfig", + "Wise": "#/components/schemas/payments_V3WiseConfig" + } + }, + { + "target": "$.components.schemas[\"wallets_Subject\"].discriminator.mapping", + "update": { + "ACCOUNT": "#/components/schemas/wallets_LedgerAccountSubject", + "WALLET": "#/components/schemas/wallets_WalletSubject" + } + }, + { + "target": "$.components.schemas[\"orchestration_Subject\"].discriminator.mapping", + "update": { + "ACCOUNT": "#/components/schemas/orchestration_LedgerAccountSubject", + "WALLET": "#/components/schemas/orchestration_WalletSubject" + } + }, + { + "target": "$.components.schemas[\"orchestration_V2Subject\"].discriminator.mapping", + "update": { + "ACCOUNT": "#/components/schemas/orchestration_V2LedgerAccountSubject", + "WALLET": "#/components/schemas/orchestration_V2WalletSubject" + } + } + ] +} diff --git a/releases/overlays/shared.overlay.yaml b/releases/overlays/shared.overlay.yaml index c98519b692..e5a371224e 100644 --- a/releases/overlays/shared.overlay.yaml +++ b/releases/overlays/shared.overlay.yaml @@ -40,96 +40,6 @@ actions: update: x-speakeasy-model-namespace: ledger - # Restore discriminator mappings after modelNamespace prefixes schema names. - # Speakeasy namespaces oneOf refs during merge, but discriminator mapping refs - # remain unchanged unless the composite overlay rewrites them explicitly. - - target: $.components.schemas.ledger_V2BulkElement.discriminator.mapping - update: - CREATE_TRANSACTION: '#/components/schemas/ledger_V2BulkElementCreateTransaction' - ADD_METADATA: '#/components/schemas/ledger_V2BulkElementAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/ledger_V2BulkElementRevertTransaction' - DELETE_METADATA: '#/components/schemas/ledger_V2BulkElementDeleteMetadata' - - target: $.components.schemas.ledger_V2BulkElementResult.discriminator.mapping - update: - CREATE_TRANSACTION: '#/components/schemas/ledger_V2BulkElementResultCreateTransaction' - ADD_METADATA: '#/components/schemas/ledger_V2BulkElementResultAddMetadata' - REVERT_TRANSACTION: '#/components/schemas/ledger_V2BulkElementResultRevertTransaction' - DELETE_METADATA: '#/components/schemas/ledger_V2BulkElementResultDeleteMetadata' - ERROR: '#/components/schemas/ledger_V2BulkElementResultError' - - target: $.components.schemas.payments_ConnectorConfig.discriminator.mapping - update: - Stripe: '#/components/schemas/payments_StripeConfig' - Dummypay: '#/components/schemas/payments_DummyPayConfig' - Wise: '#/components/schemas/payments_WiseConfig' - Modulr: '#/components/schemas/payments_ModulrConfig' - Currencycloud: '#/components/schemas/payments_CurrencyCloudConfig' - Bankingcircle: '#/components/schemas/payments_BankingCircleConfig' - Mangopay: '#/components/schemas/payments_MangoPayConfig' - Moneycorp: '#/components/schemas/payments_MoneycorpConfig' - Atlar: '#/components/schemas/payments_AtlarConfig' - Adyen: '#/components/schemas/payments_AdyenConfig' - Generic: '#/components/schemas/payments_GenericConfig' - - target: $.components.schemas.payments_V3ConnectorConfig.discriminator.mapping - update: - Adyen: '#/components/schemas/payments_V3AdyenConfig' - Atlar: '#/components/schemas/payments_V3AtlarConfig' - Bankingbridge: '#/components/schemas/payments_V3BankingbridgeConfig' - Bankingcircle: '#/components/schemas/payments_V3BankingcircleConfig' - Bitstamp: '#/components/schemas/payments_V3BitstampConfig' - Coinbaseprime: '#/components/schemas/payments_V3CoinbaseprimeConfig' - Column: '#/components/schemas/payments_V3ColumnConfig' - Currencycloud: '#/components/schemas/payments_V3CurrencycloudConfig' - Dummypay: '#/components/schemas/payments_V3DummypayConfig' - Fireblocks: '#/components/schemas/payments_V3FireblocksConfig' - Generic: '#/components/schemas/payments_V3GenericConfig' - Increase: '#/components/schemas/payments_V3IncreaseConfig' - Krakenpro: '#/components/schemas/payments_V3KrakenproConfig' - Mangopay: '#/components/schemas/payments_V3MangopayConfig' - Modulr: '#/components/schemas/payments_V3ModulrConfig' - Moneycorp: '#/components/schemas/payments_V3MoneycorpConfig' - Plaid: '#/components/schemas/payments_V3PlaidConfig' - Powens: '#/components/schemas/payments_V3PowensConfig' - Qonto: '#/components/schemas/payments_V3QontoConfig' - Routable: '#/components/schemas/payments_V3RoutableConfig' - Stripe: '#/components/schemas/payments_V3StripeConfig' - Tink: '#/components/schemas/payments_V3TinkConfig' - Wise: '#/components/schemas/payments_V3WiseConfig' - - target: $.components.schemas.wallets_Subject.discriminator.mapping - update: - ACCOUNT: '#/components/schemas/wallets_LedgerAccountSubject' - WALLET: '#/components/schemas/wallets_WalletSubject' - - target: $.components.schemas.orchestration_Subject.discriminator.mapping - update: - ACCOUNT: '#/components/schemas/orchestration_LedgerAccountSubject' - WALLET: '#/components/schemas/orchestration_WalletSubject' - - target: $.components.schemas.orchestration_V2Subject.discriminator.mapping - update: - ACCOUNT: '#/components/schemas/orchestration_V2LedgerAccountSubject' - WALLET: '#/components/schemas/orchestration_V2WalletSubject' - - # Encode Ledger query-template resources as constants so generated union - # helpers initialize the resource tag without discriminator inference. - - target: $.components.schemas.ledger_V2QueryParams.oneOf[0].properties.resource.enum - remove: true - - target: $.components.schemas.ledger_V2QueryParams.oneOf[0].properties.resource - update: - const: accounts - - target: $.components.schemas.ledger_V2QueryParams.oneOf[1].properties.resource.enum - remove: true - - target: $.components.schemas.ledger_V2QueryParams.oneOf[1].properties.resource - update: - const: transactions - - target: $.components.schemas.ledger_V2QueryParams.oneOf[2].properties.resource.enum - remove: true - - target: $.components.schemas.ledger_V2QueryParams.oneOf[2].properties.resource - update: - const: logs - - target: $.components.schemas.ledger_V2QueryParams.oneOf[3].properties.resource.enum - remove: true - - target: $.components.schemas.ledger_V2QueryParams.oneOf[3].properties.resource - update: - const: volumes - # Treat all HTTP error responses as errors. Component specs contribute their # own defaults during merge, so remove the merged value before replacing it. - target: $.paths.x-speakeasy-errors From 974dd162cbd8673ae737064593547ce59005bcf7 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Thu, 6 Aug 2026 10:15:38 +0200 Subject: [PATCH 3/4] fix(openapi): remove composition lint warnings --- .speakeasy/workflow.lock | 4 ++-- Justfile | 5 +++++ releases/overlays/shared.overlay.yaml | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index 96fe4831e8..d29c0579d3 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -2,8 +2,8 @@ speakeasyVersion: 1.791.4 sources: stacks-source: sourceNamespace: stacks-source - sourceRevisionDigest: sha256:63386e0de2492cf204e4fc939278e627a488687f6f0a26685810f5a8d08a796c - sourceBlobDigest: sha256:7654239aa54ec23ece2a055b35030708e91851bc2e476169a92d94380117e151 + sourceRevisionDigest: sha256:1d9e07120ee16749020c103aba5ef1de0601f8ee52ff065d000a1e651521898e + sourceBlobDigest: sha256:29450b93df4a9523bbe1b02552a402098ed562fe9fae90ccacfa55994fee806f tags: - latest - SDK_VERSION diff --git a/Justfile b/Justfile index fe9a51b86f..1e7f8729e5 100644 --- a/Justfile +++ b/Justfile @@ -57,6 +57,11 @@ validate-openapi: jq -e '[.components.schemas.ledger_V2QueryParams.oneOf[].properties.resource | select(.const == null or .enum != null)] | length == 0' releases/build/generate.json >/dev/null # Error responses must not be generated as successful response variants. test "$(jq -c '.paths["x-speakeasy-errors"].statusCodes' releases/build/generate.json)" = '["4XX","5XX","default"]' + # Every operation tag must be declared globally. + jq -e '[.tags[].name] as $tags | [.paths[] | to_entries[] | select(.key | IN("get", "post", "put", "patch", "delete", "options", "head", "trace")) | .value.tags[]? | select(. as $tag | $tags | index($tag) == null)] | length == 0' releases/build/generate.json >/dev/null + # Composition cleanups must remain applied when component specs change. + jq -e '.components.schemas.auth_Scope == null and .components.schemas.auth_ScopeOptions == null' releases/build/generate.json >/dev/null + jq -e '[.components.schemas[] | .enum? // empty | select(length != (unique | length))] | length == 0' releases/build/generate.json >/dev/null # Generate event schemas generate-events: diff --git a/releases/overlays/shared.overlay.yaml b/releases/overlays/shared.overlay.yaml index e5a371224e..50c9678a66 100644 --- a/releases/overlays/shared.overlay.yaml +++ b/releases/overlays/shared.overlay.yaml @@ -40,6 +40,28 @@ actions: update: x-speakeasy-model-namespace: ledger + # Keep infrastructure endpoints in the declared Ledger v2 tag group + - target: $.paths['/api/ledger/_/info'].get.tags[0] + remove: true + - target: $.paths['/api/ledger/_/info'].get.tags + update: + - ledger.v2 + - target: $.paths['/api/ledger/_/metrics'].get.tags[0] + remove: true + - target: $.paths['/api/ledger/_/metrics'].get.tags + update: + - ledger.v2 + + # Remove an orphaned Auth schema that is not referenced by any operation + - target: $.components.schemas.auth_Scope + remove: true + - target: $.components.schemas.auth_ScopeOptions + remove: true + + # Remove the duplicated FAILED value from the legacy payment status enum + - target: $.components.schemas.payments_LegacyPaymentStatus.enum[14] + remove: true + # Treat all HTTP error responses as errors. Component specs contribute their # own defaults during merge, so remove the merged value before replacing it. - target: $.paths.x-speakeasy-errors From 6d620191da6e2e5e2762d85ed43c0e49a5fcd2d8 Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Thu, 6 Aug 2026 10:18:50 +0200 Subject: [PATCH 4/4] fix(openapi): validate nested enum values --- Justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Justfile b/Justfile index 1e7f8729e5..edd1f75d77 100644 --- a/Justfile +++ b/Justfile @@ -61,7 +61,7 @@ validate-openapi: jq -e '[.tags[].name] as $tags | [.paths[] | to_entries[] | select(.key | IN("get", "post", "put", "patch", "delete", "options", "head", "trace")) | .value.tags[]? | select(. as $tag | $tags | index($tag) == null)] | length == 0' releases/build/generate.json >/dev/null # Composition cleanups must remain applied when component specs change. jq -e '.components.schemas.auth_Scope == null and .components.schemas.auth_ScopeOptions == null' releases/build/generate.json >/dev/null - jq -e '[.components.schemas[] | .enum? // empty | select(length != (unique | length))] | length == 0' releases/build/generate.json >/dev/null + jq -e '[.. | objects | select(.enum? != null) | .enum | select(length != (unique | length))] | length == 0' releases/build/generate.json >/dev/null # Generate event schemas generate-events: