Skip to content

fix: enforce per-task push notification config limit - #1044

Merged
kabir merged 6 commits into
a2aproject:mainfrom
ez-lbz:fix/push-config-limit
Aug 12, 2026
Merged

fix: enforce per-task push notification config limit#1044
kabir merged 6 commits into
a2aproject:mainfrom
ez-lbz:fix/push-config-limit

Conversation

@ez-lbz

@ez-lbz ez-lbz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What changed

1. Enforce a per-task push notification config limit

Problem: InMemoryPushNotificationConfigStore.setInfo maintained an unbounded List<TaskPushNotificationConfig> per task. A client could register unlimited push notification configs for one task; each config consumes memory and can trigger an outbound HTTP request on every task event, enabling resource exhaustion and amplified outbound traffic.

Fix (server-common/src/main/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStore.java):

  • Added MAX_PUSH_CONFIGS_PER_TASK = 100 (matching the Python SDK's cap).
  • setInfo now counts only new configs against the limit: re-registering/updating an already-registered config ID remains allowed. When a genuinely new config would exceed the limit, setInfo throws InvalidParamsError (code -32602), the same error family already used for push-config validation, which transports surface as a client error.

Fix (server-common/src/test/java/org/a2aproject/sdk/server/tasks/InMemoryPushNotificationConfigStoreTest.java):

  • testSetInfoAtLimitExactlyAllowed — the 100th config still succeeds.
  • testSetInfoRejectsExceedingPerTaskLimit — the 101st distinct config throws InvalidParamsError and the store is unchanged.
  • testSetInfoUpdateExistingConfigAtLimitAllowed — updating an existing config at the limit is still allowed.

Behavior change: registering more than 100 distinct push notification configs for a single task now fails with InvalidParamsError instead of succeeding. Updates to existing configs are unaffected.

2. Enforce the limit in the JPA store and make it configurable

Problem: The per-task limit only existed in the in-memory store, leaving JpaDatabasePushNotificationConfigStore unbounded; the limit was also hardcoded rather than operator-configurable.

Fix (server-common + extras/push-notification-config-store-database-jpa):

  • Added a2a.push-notification-config.max-per-task (default 100) to META-INF/a2a-defaults.properties, read through A2AConfigProvider.
  • Added PushNotificationConfigStore.maxPushConfigsPerTask(A2AConfigProvider) and documented the per-task limit in the interface javadoc.
  • InMemoryPushNotificationConfigStore reads the configured limit (null-safe when constructed directly).
  • JpaDatabasePushNotificationConfigStore counts existing configs for the task before persisting a new one and rejects beyond the limit with InvalidParamsError.

Testing

  • mvn -pl server-common test -Dtest=InMemoryPushNotificationConfigStoreTest — 32 tests, 0 failures.
  • mvn -pl extras/push-notification-config-store-database-jpa test — 35 tests, 0 failures, 1 skipped (pre-existing).

@kabir kabir left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi thanks @ez-lbz :-)

I think this looks good, but we need to also do the same in JpaDatabasePushNotificationConfigStore, and document it in the PushNotificationConfigStore javadoc.

* Prevents a single task from accumulating an unbounded list of configs
* (each config consumes memory and can trigger outbound HTTP requests).
*/
public static final int MAX_PUSH_CONFIGS_PER_TASK = 100;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be made configurable via A2AConfigProvider and a2a-defaults.properties. And it will need adding to the docs

}
notificationConfig = builder.build();

// Enforce the per-task limit (BUG-42). Re-registering/updating an already-registered

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is BUG-42? :-)

… in the JPA store

- Add a2a.push-notification-config.max-per-task (default 100) to
  a2a-defaults.properties, read via A2AConfigProvider.
- Add PushNotificationConfigStore.maxPushConfigsPerTask() helper and
  document the per-task limit in the interface javadoc.
- InMemoryPushNotificationConfigStore now reads the configured limit
  (null-safe for direct construction in tests).
- JpaDatabasePushNotificationConfigStore enforces the same limit before
  persisting a new config.
@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — all three points are addressed:

  1. JPA store: JpaDatabasePushNotificationConfigStore now enforces the same per-task limit, counting existing configs before persisting a new one and rejecting beyond the limit with InvalidParamsError.
  2. Configurable: the limit is now read from a2a.push-notification-config.max-per-task (added to a2a-defaults.properties, default 100) via A2AConfigProvider, with a PushNotificationConfigStore.maxPushConfigsPerTask() helper. Both stores share it.
  3. Docs: the per-task limit is documented in the PushNotificationConfigStore javadoc and in the properties file.

Testing: InMemoryPushNotificationConfigStoreTest 32 passed; JpaDatabasePushNotificationConfigStoreIntegrationTest 35 passed (1 pre-existing skip).

kabir added a commit to kabir/a2a-java that referenced this pull request Aug 11, 2026
Add the `a2a.push-notification-config.max-per-task` property to the
configuration docs page, complementing PR a2aproject#1044 which introduces
per-task push notification config limits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kabir

kabir commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

@ez-lbz ez-lbz#11 has the missing doc

@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for adding the missing configuration documentation — merged. The a2a.push-notification-config.max-per-task property is now documented in the configuration guide alongside the javadoc.

@kabir

kabir commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Sorry @ez-lbz, Claude added the doc in the wrong place. ez-lbz#12 should fix it

@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

No problem — thanks for moving it to the dev docs; merged. Makes sense since the property isn't in the 1.2.0 release yet.

@kabir kabir left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all three items from the first review — JPA store enforcement, interface javadoc, and configurability + docs all look good.

Things that look solid:

  • maxPushConfigsPerTask() is well-designed — null-safe for non-CDI usage, handles bad input gracefully
  • The "update existing config at limit" edge case is handled correctly in both stores
  • Tests cover the three key scenarios (at limit, exceeding, update at limit)
  • Docs placed in docs/content/dev/ (correct location)
  • Limit check happens before insert in both stores, so the store stays unchanged on rejection

Two suggestions:

  1. No JPA-side test for the limit enforcement. The in-memory store has 3 tests for the limit, but JpaDatabasePushNotificationConfigStoreIntegrationTest doesn't exercise the new limit path. If the count query has a typo or the >= comparison is wrong, there's no test catching it. A test that registers configs up to the limit and verifies the next one throws InvalidParamsError would close this gap.

  2. Minor: magic number 100 repeated 3 times in maxPushConfigsPerTask() (PushNotificationConfigStore.java). The constant MAX_PUSH_CONFIGS_PER_TASK exists on InMemoryPushNotificationConfigStore but can't be referenced from the interface without a reverse dependency. Consider defining DEFAULT_MAX_PUSH_CONFIGS_PER_TASK on the interface itself and using it in both places.

…tant

- Define DEFAULT_MAX_PUSH_CONFIGS_PER_TASK on PushNotificationConfigStore
  and use it in maxPushConfigsPerTask() (removes the repeated magic 100);
  InMemoryPushNotificationConfigStore.MAX_PUSH_CONFIGS_PER_TASK now
  references it.
- Add JpaDatabasePushNotificationConfigStoreIntegrationTest coverage for
  the per-task limit: registering up to the limit succeeds, the next
  distinct config throws InvalidParamsError, and updating an existing
  config ID at the limit is still allowed.
@ez-lbz
ez-lbz force-pushed the fix/push-config-limit branch from 847257f to 8622f32 Compare August 11, 2026 16:15
Restore the documentation for a2a.push-notification-config.max-per-task in
docs/content/dev/configuration.md (previously contributed via the fork PR
that was inadvertently dropped during a force-push).
@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Both suggestions addressed:

  1. JPA-side limit test: added testPushConfigLimitEnforced to JpaDatabasePushNotificationConfigStoreIntegrationTest — registers configs up to the limit, asserts the next distinct config throws InvalidParamsError, and verifies updating an existing config ID at the limit is still allowed.
  2. Magic number: added PushNotificationConfigStore.DEFAULT_MAX_PUSH_CONFIGS_PER_TASK = 100 and used it in maxPushConfigsPerTask(); InMemoryPushNotificationConfigStore.MAX_PUSH_CONFIGS_PER_TASK now references it (no reverse dependency).

Testing: JPA integration 21 passed (incl. the new limit test); InMemory 32 passed. The a2a.push-notification-config.max-per-task doc is in docs/content/dev/configuration.md.

Rename `config` field to `configProvider` to avoid shadowing by
local variables of the same name in setInfo and deleteInfo.

Combine the existing-config check and the remove-old-config loop
into a single `removeIf` call, eliminating a redundant iteration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@kabir

kabir commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Hi, I opened ez-lbz#13 with the remainder of the PR feedback

refactor: simplify setInfo limit check and fix field shadowing
@ez-lbz

ez-lbz commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Merged the refactor (#13) — thanks for the field-shadowing fix and the single-pass limit check.

@kabir
kabir merged commit b478810 into a2aproject:main Aug 12, 2026
14 of 15 checks passed
@kabir

kabir commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks @ez-lbz!

BTW it seems we have a new job installed at org level which checks if commits are signed off. See https://github.com/a2aproject/a2a-java/pull/1044/checks?check_run_id=93965391402. It is fine for now, and I have not been doing this myself either, but will start now. So please do this for new work, the PRs in the queue are probably fine.

Also, you might want to use your SSH key to sign the commits as outlined in here https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#ssh-commit-signature-verification this is so that they show as 'Verified' when you look at the commit history in GitHub :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants