Skip to content

feat(#5340): A2A TaskExpirer + MetaAgentCardRegistry + failure-mode test (D2a) - #5346

Merged
qqeasonchen merged 1 commit into
developfrom
fix/5340-a2a-readiness-d2a
Sep 8, 2026
Merged

qqeasonchen merged 1 commit into
developfrom
fix/5340-a2a-readiness-d2a

Conversation

@qqeasonchen

Copy link
Copy Markdown
Contributor

A2A TaskExpirer + MetaAgentCardRegistry + failure-mode test (D2a)

Q4 asks: are task expiry / reaping, Meta-backed AgentCard persistence, Runtime-dispatch integration, and real Meta / Runtime failure testing complete? Until they are, A2A remains explicitly Experimental.

This PR lands D2a — four of the five acceptance items from the #5340 issue body. D2b (Testcontainers E2E) is a follow-up; the failure-mode test in D2a pins the gateway's contract at the user-facing API, and D2b will exercise the same scenarios end-to-end against a real Meta + broker + multi-instance EM.

What this PR adds

  1. eventmesh-runtime/.../a2a/TaskExpirer.java — new reaper class with configurable TTL (default 24h) and scan interval (default 60s). Calls TaskStore.expireStale() on a daemon-thread ScheduledExecutor. Exposes a scan() method for tests to drive the reaper synchronously. Idempotent start/shutdown. Wired into A2AGatewayService via a new opt-in setTaskExpirer() setter; gateway.start() starts the reaper, gateway.shutdown() stops it. The reaper is opt-in so existing deployments that do not want a background scanner are not affected.

  2. eventmesh-runtime/.../a2a/MetaAgentCardRegistry.java — new cluster-shared AgentCard persistence backed by MetaStore, using Jackson JSON for the wire value (the AgentCard model is already Jackson-friendly via @Data @Builder). Per-process cache kept fresh by a Meta watch on /em/agent-cards/. Malformed values are logged + ignored so a peer's bad write does not poison the local cache. Same pattern as ClusterSubscriptionStore (Sub-PR A).

  3. eventmesh-runtime/.../a2a/A2AGatewayService.java — new setTaskExpirer() / getTaskExpirer() setter pair (the existing 6-arg ctor is unchanged for backward compatibility). start() / shutdown() call through to the reaper when it is attached. Bug fix: submitTask now wraps taskStore.createTask in try/catch and returns an exceptionally completed CompletableFuture on RuntimeException (e.g. MetaPartitionException from a Meta-backed TaskStore), so the public API is uniformly future-based. Callers no longer have to catch a synchronous throw from submitTask. This is the contract the new failure-mode test pins.

  4. eventmesh-runtime/.../a2a/TaskExpirerTest.java — 6 cases: evicts idle tasks, fresh tasks are not evicted, listener receives evicted taskIds, start/shutdown are idempotent, background scan fires on schedule, invalid TTL/interval throws.

  5. eventmesh-runtime/.../a2a/MetaAgentCardRegistryTest.java — 5 cases: register/lookup/remove, wrapper restart, two-instance shared Meta, malformed JSON is logged + ignored, null args are rejected.

  6. eventmesh-runtime/.../a2a/A2AGatewayFailureModeTest.java — 2 cases: submitTask surfaces Meta failure to the caller (the returned future completes exceptionally with MetaPartitionException, NOT hung, NOT silently dropped); submitTask succeeds after Meta heals. This is the A2A-layer counterpart of the TaskStore-level failure test in StateStoreDurabilityTest (issue [Architecture Review] State store durability: restart / multi-instance / fencing / cross-store failure tests #5339).

  7. docs/a2a-readiness-decision.md — decision log: keep A2A Experimental for the next release. Explicit promotion criteria (D2b Testcontainers E2E must be green for >=1 release, plus a 30-day production-style deployment, plus an updated docs/a2a-protocol.md, plus a deprecation note for any legacy A2A path).

Acceptance status (issue #5340)

  • TaskExpirer reaper lands with a unit test (D2a, this PR).
  • MetaAgentCardRegistry lands with restart-recovery test (D2a, this PR).
  • Testcontainers E2E eventmesh-a2a-e2e runs in CI on PRs to eventmesh-a2a/ (D2b follow-up, tracked under a new issue).
  • Failure-mode test (Meta down → A2A surfaces error) exists (D2a, this PR).
  • Decision recorded: keep A2A Experimental (D2a, this PR, docs/a2a-readiness-decision.md).

Out of scope (D2a explicitly does NOT do)

Part of #5296
Sub-PR of #5302
Closes #5340

…est (D2a)

Part of #5296 (Architecture Review, 'New review questions' 2026-09-07, Q4).
Sub-PR D2a of #5302 (A2A onto Runtime).

Q4 asks: are task expiry / reaping, Meta-backed AgentCard persistence,
Runtime-dispatch integration, and real Meta / Runtime failure testing
complete? Until they are, A2A remains explicitly Experimental.

This PR lands D2a: four of the five acceptance items from the #5340
issue body. D2b (Testcontainers E2E) is a follow-up; the failure-mode
test in D2a pins the gateway's contract at the user-facing API, and D2b
will exercise the same scenarios end-to-end against a real Meta +
broker + multi-instance EM.

## What this PR adds

1. eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/TaskExpirer.java
   - new reaper class with configurable TTL (default 24h) and scan
     interval (default 60s). Calls TaskStore.expireStale() on a
     daemon-thread ScheduledExecutor. Exposes a scan() method for
     tests to drive the reaper synchronously. Idempotent
     start/shutdown.
   - wired into A2AGatewayService via a new opt-in setTaskExpirer()
     setter; gateway.start() starts the reaper, gateway.shutdown()
     stops it. The reaper is opt-in so existing deployments that
     do not want a background scanner are not affected.

2. eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/MetaAgentCardRegistry.java
   - new cluster-shared AgentCard persistence backed by MetaStore,
     using Jackson JSON for the wire value (the AgentCard model is
     already Jackson-friendly via @DaTa @builder).
   - per-process cache kept fresh by a Meta watch on
     /em/agent-cards/. Malformed values are logged + ignored so a
     peer's bad write does not poison the local cache.
   - same pattern as ClusterSubscriptionStore (Sub-PR A).

3. eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/a2a/A2AGatewayService.java
   - new setTaskExpirer() / getTaskExpirer() setter pair (the
     existing 6-arg ctor is unchanged for backward compatibility).
   - start() / shutdown() call through to the reaper when it is
     attached. The reaper is reset on shutdown so a re-started
     gateway does not inherit a stale reaper instance.

4. eventmesh-runtime/src/test/java/org/apache/eventmesh/runtime/a2a/TaskExpirerTest.java
   - 6 cases: evicts idle tasks, fresh tasks are not evicted,
     listener receives evicted taskIds, start/shutdown are
     idempotent, background scan fires on schedule, invalid
     TTL/interval throws.

5. eventmesh-runtime/src/test/java/org/apache/eventmesh/runtime/a2a/MetaAgentCardRegistryTest.java
   - 5 cases: register/lookup/remove, wrapper restart, two-instance
     shared Meta, malformed JSON is logged + ignored, null args are
     rejected.

6. eventmesh-runtime/src/test/java/org/apache/eventmesh/runtime/a2a/A2AGatewayFailureModeTest.java
   - 2 cases: submitTask surfaces Meta failure to the caller (the
     returned future completes exceptionally with MetaPartitionException,
     NOT hung, NOT silently dropped); submitTask succeeds after
     Meta heals.
   - the A2A-layer counterpart of the TaskStore-level failure test
     in StateStoreDurabilityTest (issue #5339).

7. docs/a2a-readiness-decision.md
   - decision log: keep A2A Experimental for the next release.
   - explicit promotion criteria (D2b Testcontainers E2E must be
     green for >=1 release, plus a 30-day production-style
     deployment, plus an updated docs/a2a-protocol.md, plus a
     deprecation note for any legacy A2A path).

## Out of scope (D2b follow-up)

* Testcontainers E2E (live Nacos + broker + 2-3 EM instances). Will
  be tracked under a new issue filed off the #5340 closure.
* A2A auth / RBAC (separate concern; not part of #5296).
* A2A load-testing.

## Acceptance status (issue #5340)

- [x] TaskExpirer reaper lands with a unit test (D2a, this PR).
- [x] MetaAgentCardRegistry lands with restart-recovery test (D2a,
      this PR).
- [ ] Testcontainers E2E eventmesh-a2a-e2e runs in CI on PRs to
      eventmesh-a2a/ (D2b follow-up).
- [x] Failure-mode test (Meta down -> A2A surfaces error) exists
      (D2a, this PR).
- [x] Decision recorded: keep A2A Experimental (D2a, this PR).

Part of #5296
Sub-PR of #5302
Closes #5340

Retry-CI: Retry-CI-after-outage-1788836620
@qqeasonchen
qqeasonchen force-pushed the fix/5340-a2a-readiness-d2a branch from 3db5f66 to 72a1a01 Compare September 8, 2026 03:03
qqeasonchen added a commit that referenced this pull request Sep 8, 2026
Closes #5337 (Q1 production validation evidence + Q8 closure criteria).

Links every closed sub-issue of #5296 to its PR, squash-commit hash, test
files, test command, CI run, backend/version, deployment topology, and
observed result. The 7 reliability scenarios called out in #5337 (Q1)
are answered in the "Scenarios" section with concrete PR + test refs.

Status legend distinguishes MERGED (PR landed; CI on record before the
Actions platform outage) from OPEN-CI-startup_failure (PR open; CI
blocked by a repository-level runner availability issue affecting all
workflows since 2026-09-07 16:23 UTC). 19 PRs are catalogued, of which
16 are MERGED and 3 are OPEN-CI-startup_failure (#5345 / #5346 / #5348,
all blocked on the same platform issue).

Co-authored-by: qqeasonchen <qqeasonchen@gmail.com>

Retry-CI: Retry-CI-after-outage-1788836620
@qqeasonchen
qqeasonchen merged commit 40a6497 into develop Sep 8, 2026
1 check passed
@qqeasonchen
qqeasonchen deleted the fix/5340-a2a-readiness-d2a branch September 8, 2026 03:11
qqeasonchen added a commit that referenced this pull request Sep 8, 2026
…ues (#5349)

Closes #5337 (Q1 production validation evidence + Q8 closure criteria).

Links every closed sub-issue of #5296 to its PR, squash-commit hash, test
files, test command, CI run, backend/version, deployment topology, and
observed result. The 7 reliability scenarios called out in #5337 (Q1)
are answered in the "Scenarios" section with concrete PR + test refs.

Status legend distinguishes MERGED (PR landed; CI on record before the
Actions platform outage) from OPEN-CI-startup_failure (PR open; CI
blocked by a repository-level runner availability issue affecting all
workflows since 2026-09-07 16:23 UTC). 19 PRs are catalogued, of which
16 are MERGED and 3 are OPEN-CI-startup_failure (#5345 / #5346 / #5348,
all blocked on the same platform issue).

Co-authored-by: qqeasonchen <qqeasonchen@gmail.com>

Retry-CI: Retry-CI-after-outage-1788836620
qqeasonchen added a commit that referenced this pull request Sep 8, 2026
Phase 0 enforcement of the production-HA plan (#5354): removes both
silent InMemoryMetaStore fallbacks, adds two ArchUnit guardrails, and
repairs the compile/test/checkstyle breaks that landed on develop via
the 2026-09-07/08 Actions-outage blind merges (all found by running
the full CI build locally).

Boot changes (the #5356 core):
- UniRuntime.startPartitionOwnership: clusterMeta == null now throws
  IllegalStateException instead of new InMemoryMetaStore().
- EventMeshApplication: unsupported meta type in cluster mode fails
  fast; single-instance mode keeps the documented in-memory store.

ArchUnit guardrails (12 -> 14 rules):
- ruleInMemoryMetaStoreOnlyFromBoot
- rulePartitionOwnershipOnlyFromBootAndCluster (boot/cluster/ingress/admin)

Outage blind-merge repairs:
- missing DeliveryTopology import (#5344): compileJava failed on develop
- guard module missing rocketmq5 dep + lz4-java capability conflict
  (org.lz4 vs at.yawk.lz4) for FakeStorageCanary (#5348)
- FakeStorageCanary in storage.fakeplugin.. never matched the
  ruleStoragePluginsIsolated that-clause (#5348): moved to
  storage.kafka test package
- ClusterSubscriptionStore left empty topic buckets after last
  subscriber removal (#5345): topics() returned ghosts; buckets now
  dropped when empty
- checkstyle (maxWarnings=0) violations in #5345/#5346 test files:
  unused imports (MetaListener, StandardCharsets, TaskStore, MetaStore,
  OffsetStore), import order (A2AMessageTransport, org.junit before
  io.cloudevents), local var names (aView/bView -> viewA/viewB),
  declaration-usage distance (reaper x2, SessionRegistry b)

Full local CI parity (Temurin 21.0.11, same tasks as .github/workflows):
- ./gradlew clean generateGrammarSource            -> BUILD SUCCESSFUL
- ./gradlew :eventmesh-architecture-guard:architectureCheck --no-daemon
                                                  -> BUILD SUCCESSFUL
- ./gradlew clean build dist jacocoTestReport --parallel --daemon
    -x spotlessJava -x generateGrammarSource -x generateDistLicense
    -x checkDeniedLicense -x :eventmesh-architecture-guard:test
                                                  -> BUILD SUCCESSFUL (511 tasks, 10m47s)
- ./gradlew installPlugin                         -> BUILD SUCCESSFUL

Refs #5356 (sub-issue of #5354).

Co-authored-by: qqeasonchen <qqeasonchen@gmail.com>
qqeasonchen added a commit that referenced this pull request Sep 8, 2026
…e + arch-guard (#5367)

Phase 0 enforcement of the production-HA plan (#5354): removes both
silent InMemoryMetaStore fallbacks, adds two ArchUnit guardrails, and
repairs the compile/test/checkstyle breaks that landed on develop via
the 2026-09-07/08 Actions-outage blind merges (all found by running
the full CI build locally).

Boot changes (the #5356 core):
- UniRuntime.startPartitionOwnership: clusterMeta == null now throws
  IllegalStateException instead of new InMemoryMetaStore().
- EventMeshApplication: unsupported meta type in cluster mode fails
  fast; single-instance mode keeps the documented in-memory store.

ArchUnit guardrails (12 -> 14 rules):
- ruleInMemoryMetaStoreOnlyFromBoot
- rulePartitionOwnershipOnlyFromBootAndCluster (boot/cluster/ingress/admin)

Outage blind-merge repairs:
- missing DeliveryTopology import (#5344): compileJava failed on develop
- guard module missing rocketmq5 dep + lz4-java capability conflict
  (org.lz4 vs at.yawk.lz4) for FakeStorageCanary (#5348)
- FakeStorageCanary in storage.fakeplugin.. never matched the
  ruleStoragePluginsIsolated that-clause (#5348): moved to
  storage.kafka test package
- ClusterSubscriptionStore left empty topic buckets after last
  subscriber removal (#5345): topics() returned ghosts; buckets now
  dropped when empty
- checkstyle (maxWarnings=0) violations in #5345/#5346 test files:
  unused imports (MetaListener, StandardCharsets, TaskStore, MetaStore,
  OffsetStore), import order (A2AMessageTransport, org.junit before
  io.cloudevents), local var names (aView/bView -> viewA/viewB),
  declaration-usage distance (reaper x2, SessionRegistry b)

Full local CI parity (Temurin 21.0.11, same tasks as .github/workflows):
- ./gradlew clean generateGrammarSource            -> BUILD SUCCESSFUL
- ./gradlew :eventmesh-architecture-guard:architectureCheck --no-daemon
                                                  -> BUILD SUCCESSFUL
- ./gradlew clean build dist jacocoTestReport --parallel --daemon
    -x spotlessJava -x generateGrammarSource -x generateDistLicense
    -x checkDeniedLicense -x :eventmesh-architecture-guard:test
                                                  -> BUILD SUCCESSFUL (511 tasks, 10m47s)
- ./gradlew installPlugin                         -> BUILD SUCCESSFUL

Refs #5356 (sub-issue of #5354).
qqeasonchen added a commit that referenced this pull request Sep 17, 2026
Bring the single source of truth up to date with what actually landed:

- SSE / WS push: unified ACK-tracked redelivery + DLQ now shared with
  long-polling via ReliableDispatcher; real-broker e2e suite in #5389
- Connector Runtime: all 23 plugins fully implemented since #5394 (the
  "rest are templates" note is stale); unit tests still cover only
  file/kafka/pulsar/rocketmq
- A2A: task reaper + Meta-backed agent cards landed in #5346, quota
  classification in #5373; Beta promotion gated on Testcontainers E2E
  (#5340) instead of "pending" items
- add the memory storage backend row (default, zero-dependency dev/CI;
  #5400) and mention it in the features list, both languages
qqeasonchen added a commit that referenced this pull request Sep 17, 2026
…in, A2A to 10108, 10205 gRPC reserved

8080/8081 collide with the most common dev-service ports (Tomcat, Jenkins,
Spring Boot apps, proxies). Move back onto the project's own 1.x default
port block, which keeps long-time users on familiar numbers:/n
- traffic HTTP 8080 -> 10105 (all app protocols multiplex on it, incl.
  the legacy /eventmesh/* bridge)
- admin HTTP    8081 -> 10106
- WebSocket     8082 -> 10107 (opt-in, unchanged default -1)
- A2A Gateway   10105 -> 10108 (frees 10105 for the CORE runtime; the
  experimental gateway takes the adjacent 10108)
- 10205 documented as RESERVED for a future gRPC protocol port (the 1.x
  default): the uni runtime does not ship a gRPC server yet, and the
  port must not be casually taken so a later gRPC addition lands on the
  historical number

Touched: EventMeshApplication defaults, bin/start.sh, docker/Dockerfile(+.connector),
K8s manifests + README, SDK CloudEventsClient default URL + javadocs, A2AClient /
ExampleConstants / A2AGatewayDemo, e2e test literals, docs (quickstart,
configuration, deployment, a2a) and eventmesh.properties comments.

Configuration keys are unchanged - only defaults move; existing
deployments pinning the old ports keep working.

Also point image-smoke-test.yml at the new ports throughout (host = container,
10105/10106 - no offset mapping left).

README / README.zh-CN quick-start port mentions updated too.

Feature docs swept too (client-java, pubsub, streaming, introduction, index,
admin-api, http-api, observability, connectors overview) - connector-local
webhook ports are untouched.

Fix a port-sweep typo (10100 -> 10105) in the documentation map.

README / README.zh-CN capability-status refreshed against the current tree:/n- SSE/WS push: ACK-tracked redelivery + DLQ shared with long-polling
  (ReliableDispatcher); e2e-real-broker suite in #5389
- Connector Runtime: all 23 plugins fully implemented since #5394 (no
  template stubs left); 4 of 23 still carry the only unit tests
- A2A Gateway: reaper + Meta-backed agent cards landed #5346, quota #5373;
  Beta promotion gated on Testcontainers E2E (#5340)
- new Memory storage row (Beta, zero-dependency dev/CI default, #5400)
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.

[Architecture Review] A2A readiness: TaskExpirer, Meta-backed AgentCard, Runtime E2E, failure testing

1 participant