diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index c0a04b5..86c963e 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -69,26 +69,13 @@ jobs: java-version: '8' distribution: temurin - - name: Install Doppler CLI - uses: dopplerhq/cli-action@4819d808ab99e5cde19a0637a16536a4038fad73 # v4 - - name: Integration Tests env: - DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN_PROD }} - run: | - client_id="$(doppler secrets get PLUGGY_CLIENT_ID --plain)" - client_secret="$(doppler secrets get PLUGGY_CLIENT_SECRET --plain)" - base_url="$(doppler secrets get PLUGGY_BASE_URL --plain)" - - printf '::add-mask::%s\n' "$client_id" - printf '::add-mask::%s\n' "$client_secret" - printf '::add-mask::%s\n' "$base_url" - unset DOPPLER_TOKEN - - PLUGGY_CLIENT_ID="$client_id" \ - PLUGGY_CLIENT_SECRET="$client_secret" \ - PLUGGY_BASE_URL="$base_url" \ - mvn -B verify --file pom.xml + PLUGGY_CLIENT_ID: ${{ secrets.PLUGGY_CLIENT_ID }} + PLUGGY_CLIENT_SECRET: ${{ secrets.PLUGGY_CLIENT_SECRET }} + PLUGGY_BASE_URL: ${{ secrets.PLUGGY_BASE_URL }} + PLUGGY_RSA_PUBLIC_KEY: ${{ secrets.PLUGGY_RSA_PUBLIC_KEY }} + run: mvn -B verify --file pom.xml deploy: runs-on: ubuntu-latest needs: [validate-ref, build, test] diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 38ad9c4..a74783d 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -46,23 +46,10 @@ jobs: java-version: '8' distribution: temurin - - name: Install Doppler CLI - uses: dopplerhq/cli-action@4819d808ab99e5cde19a0637a16536a4038fad73 # v4 - - name: Integration Tests env: - DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN_PROD }} - run: | - client_id="$(doppler secrets get PLUGGY_CLIENT_ID --plain)" - client_secret="$(doppler secrets get PLUGGY_CLIENT_SECRET --plain)" - base_url="$(doppler secrets get PLUGGY_BASE_URL --plain)" - - printf '::add-mask::%s\n' "$client_id" - printf '::add-mask::%s\n' "$client_secret" - printf '::add-mask::%s\n' "$base_url" - unset DOPPLER_TOKEN - - PLUGGY_CLIENT_ID="$client_id" \ - PLUGGY_CLIENT_SECRET="$client_secret" \ - PLUGGY_BASE_URL="$base_url" \ - mvn -B verify --file pom.xml + PLUGGY_CLIENT_ID: ${{ secrets.PLUGGY_CLIENT_ID }} + PLUGGY_CLIENT_SECRET: ${{ secrets.PLUGGY_CLIENT_SECRET }} + PLUGGY_BASE_URL: ${{ secrets.PLUGGY_BASE_URL }} + PLUGGY_RSA_PUBLIC_KEY: ${{ secrets.PLUGGY_RSA_PUBLIC_KEY }} + run: mvn -B verify --file pom.xml diff --git a/CLAUDE.md b/CLAUDE.md index 84d779e..55400e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,12 +37,12 @@ Three slash commands under `.claude/commands/` codify the recurring SDK ↔ API - `add-model-fields` — Java/Lombok templates for adding fields to existing DTOs, with the Gson `IDENTITY` casing gotcha called out. - `add-endpoint` — templates for adding a new endpoint method to `PluggyApiService` with the right Retrofit annotations and DTO naming conventions. -Integration tests require the following env vars (loaded from Doppler in CI, see `.github/workflows/maven.yml`): +Integration tests require the following env vars. In CI they are repository secrets synced from Doppler (change values in Doppler, not in GitHub: the sync overwrites them) and passed only to the integration-test step, see `.github/workflows/maven.yml`: - `PLUGGY_CLIENT_ID` - `PLUGGY_CLIENT_SECRET` - `PLUGGY_BASE_URL` -- `PLUGGY_RSA_PUBLIC_KEY` (optional; only needed when exercising `EncryptedParametersInterceptor`) +- `PLUGGY_RSA_PUBLIC_KEY` (optional; only needed when exercising `EncryptedParametersInterceptor`. Without it, `CreateItemTest#createItem_withEncryptedParameters_responseOk` still passes but sends the parameters unencrypted) Missing any of the first three causes `BaseApiIntegrationTest` to fail-fast in `@BeforeEach`. Each test that creates items appends the new id to `itemsIdCreated`; the base class's `@AfterEach` deletes them, so integration tests are responsible for tracking what they create. @@ -62,7 +62,7 @@ The payments surface is Smart Transfers (`/smart-transfers/*`) and Pix por biome **3. Two interceptors handle cross-cutting concerns** -- `ApiKeyAuthInterceptor` (`client/auth/`): transparently fetches the `x-api-key` JWT via `POST /auth` on first use, caches it in `TokenProvider`, decodes the JWT `exp` claim to detect expiry, and on a 401/403 with an expired key refreshes once and retries the original request. An `x-api-key` already on the request is the caller's credential: it is sent as-is and never refreshed or replaced with the team key. Also sets `User-Agent: PluggyJava/` — note this string is hardcoded in two places (`PluggyClient.authenticate` and `ApiKeyAuthInterceptor.requestWithAuth`) and is not auto-derived from `pom.xml`. +- `ApiKeyAuthInterceptor` (`client/auth/`): transparently fetches the `x-api-key` JWT via `POST /auth` on first use, caches it in `TokenProvider`, decodes the JWT `exp` claim to detect expiry, and on a 401/403 with an expired key refreshes once and retries the original request. An `x-api-key` already on the request is the caller's credential: it is sent as-is and never refreshed or replaced with the team key. Also sets `User-Agent: PluggyJava/ (Java )` via `ai.pluggy.utils.Version`, which reads `project.version` from the filtered `pluggy.properties`, so bumping `pom.xml` is all a release needs. - `EncryptedParametersInterceptor` (`client/auth/`): only attached when `rsaPublicKey(...)` is set on the builder. RSA/ECB/OAEPPadding-encrypts the `parameters` JSON field on POST/PATCH `/items` requests before they leave OkHttp. All three conditions are required: Smart Transfer preauthorizations also send a `parameters` field, which must go out as plain JSON, and item requests without one (MFA) must pass through. ### JSON / Gson conventions — important gotcha @@ -86,9 +86,9 @@ The version in `pom.xml` (`` on line 7) is the **single source of truth bump in pom.xml ──► merge to master │ ▼ -release.yml (on: push to master; also workflow_dispatch w/ `force`) +release.yml (on: push to master; also workflow_dispatch) reads project.version → TAG = v - if TAG already exists (and not forced): NO-OP, nothing is published + if TAG already exists: NO-OP, nothing is published else: creates annotated tag v, generates notes from git log ..HEAD, publishes a GitHub Release │ ⚠️ the "created" event does NOT auto-run maven-publish (see gotchas) @@ -96,7 +96,7 @@ release.yml (on: push to master; also workflow_dispatch w/ `force`) maven-publish.yml (declares `on: release created`, but in practice must be run MANUALLY via workflow_dispatch w/ `tag_version`) build : mvn -B package - test : mvn -B verify (integration tests, creds from Doppler) + test : mvn -B verify (integration tests, creds from repo secrets) deploy : mvn deploy -Dmaven.test.skip.exec -s settings.xml → GitHub Packages (maven.pkg.github.com/pluggyai/pluggy-java) (deploy job needs `packages: write`) @@ -112,10 +112,10 @@ maven-publish.yml (declares `on: release created`, but in practice must be **Gotchas:** - **The GitHub Release does NOT auto-publish to Packages — you must dispatch `maven-publish.yml` yourself.** `release.yml` creates the Release using the default `GITHUB_TOKEN`, and GitHub deliberately does not fire workflow triggers (including `maven-publish`'s `release: created`) for events raised by `GITHUB_TOKEN`, to prevent recursive runs. So the deploy step never triggers automatically — every real publish has been a manual `workflow_dispatch`. After tagging, run `gh workflow run maven-publish.yml -f tag_version=v`. A tagged release with nothing in Packages is this bug, not a build failure. *Permanent fix (not yet applied): create the Release with a PAT instead of `GITHUB_TOKEN`, or add a step at the end of `release.yml` that dispatches `maven-publish.yml`.* -- **CI uses a GitHub Actions allowlist (`allowed_actions: selected`).** Only GitHub-owned actions plus a short pinned list (currently `dopplerhq/cli-action@v1`, `softprops/action-gh-release@v1`) are permitted. Any non-allowlisted action — or a *bumped version* of an allowlisted one (e.g. `dopplerhq/cli-action@v4`) — makes the whole workflow fail at **`startup_failure` with no jobs**. This is why `dependabot.yml` ignores `semver-major`; if a workflow suddenly won't start, check `gh api repos/pluggyai/pluggy-java/actions/permissions/selected-actions` and either pin back or add the action (repo admin: Settings → Actions → Allowed actions). +- **CI uses a GitHub Actions allowlist (`allowed_actions: selected`).** The workflows use only GitHub-owned actions, pinned to full commit SHAs. Any other action must be allowlisted at that exact SHA; otherwise the whole workflow fails at **`startup_failure` with no jobs**. If a workflow suddenly won't start, check `gh api repos/pluggyai/pluggy-java/actions/permissions/selected-actions` and either drop the action or allowlist its exact pin (repo admin: Settings → Actions → Allowed actions). - **Forgetting the bump is a silent no-op.** If the pom version already has a tag, the push to master publishes nothing. Don't bump the version unless you intend to cut a release, and *do* bump it when you do. - The `maven-release-plugin` is configured in `pom.xml` (`tagNameFormat = v@{project.version}`) but is **not** used by CI — tagging is done by `release.yml`'s shell, not `mvn release:*`. -- Manual escape hatches: run `release.yml` via `workflow_dispatch` with `force=true` to re-release an existing tag; run `maven-publish.yml` via `workflow_dispatch` with a `tag_version` input to re-deploy an existing tag without re-tagging. +- Manual escape hatch: run `maven-publish.yml` via `workflow_dispatch` with a `tag_version` input (validated as a `v` tag) to re-deploy an existing tag without re-tagging. Published tags are immutable: `release.yml` has no way to move or re-create one. ## Style diff --git a/pom.xml b/pom.xml index 8c5f6a3..157a93e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ai.pluggy pluggy-java - 1.17.1 + 1.17.2 jar @@ -60,14 +60,14 @@ com.squareup.retrofit2 converter-gson - 2.9.0 + 2.12.0 com.google.code.gson gson - 2.11.0 + 2.13.1