Skip to content

ts: MeshJob event injection parity — recvEvent/sendEvent/postEvent - #1043

Merged
dhyansraj merged 3 commits into
mainfrom
feature/1042-ts-meshjob-event-parity
May 18, 2026
Merged

ts: MeshJob event injection parity — recvEvent/sendEvent/postEvent#1043
dhyansraj merged 3 commits into
mainfrom
feature/1042-ts-meshjob-event-parity

Conversation

@dhyansraj

@dhyansraj dhyansraj commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #1042. TypeScript vertical for the consumer-pushed event-injection primitive that shipped Python-first in #1041 (closes #1032).

Follows the original MeshJob cross-runtime shipping cadence: Python (#861/#878) → TypeScript (#885) → Java (#891). This PR is the TS step; Java parity will follow as a separate PR.

What's in the PR

NAPI bindings (src/runtime/core/src/jobs_napi.rs)

  • JsJobController::recv_event(types?, timeoutSecs?) -> Promise<JobEvent | null>
  • JsJobProxy::send_event(eventType, payload) -> Promise<JobEventReceipt>
  • parse_timeout_secs helper guards NaN/Infinity/negative inputs — applied at both recv_event AND existing wait() call sites
  • JobError::JobTerminal mapped to a TS-visible named error via the job_error_to_napi remap

TypeScript SDK (src/runtime/typescript/src/)

  • recvEvent / sendEvent surfaced on the MeshJob interface in types.ts
  • New mesh.jobs namespace in jobs.ts with postEvent(jobId, eventType, payload) convenience helper
  • LRU-bounded JobProxy cache (default 256, env override MCP_MESH_JOBPROXY_CACHE_MAX) — mirrors Python implementation. JS Map insertion-order semantics make O(1) LRU straightforward — no extra bookkeeping needed.
  • JobNotFoundError / JobTerminalError typed error classes
  • JobEvent / JobEventReceipt type definitions
  • Exports wired in index.ts (mesh.jobs.postEvent, error classes, types)

Tests

  • TS unit: 16 new tests in jobs.spec.ts covering recvEvent / sendEvent / postEvent, LRU eviction (cap=2, populate 3, assert oldest evicted), typed-error mapping, NaN/Inf timeout guards, MCP_MESH_REGISTRY_URL discovery, cache keyed by (registryUrl, jobId). 734 → 750 total
  • Integration suite uc22_meshjob_ts: 3 new test cases mirror Python tc24/25/26:
    • tc24_event_injection_happy_path_ts — payload round-trip
    • tc25_event_type_filter_ts — registry-side WHERE filter
    • tc26_cancel_posts_synthetic_event_ts — cancel-grace verified in TS path
    • Reuses existing long-task-provider-ts / long-task-consumer-ts fixtures (no new fixture agents)
    • 20 baseline + 3 new = 23/23 pass

API parity matrix (TS ↔ Python)

API TypeScript Python
Producer wait for event await job.recvEvent(types, timeoutSecs) await job.recv_event(types, timeout_secs)
Consumer post to specific proxy await proxy.sendEvent(type, payload) await proxy.send_event(type, payload)
Convenience helper mesh.jobs.postEvent(...) mesh.jobs.post_event(...)
Typed errors JobNotFoundError, JobTerminalError JobNotFoundError, JobTerminalError
Proxy cache cap MCP_MESH_JOBPROXY_CACHE_MAX (256) MCP_MESH_JOBPROXY_CACHE_MAX (256)
Timeout validation Error on NaN/Inf/neg ValueError on NaN/Inf/neg

Cancel synthetic event grace is server-side (MCP_MESH_CANCEL_EVENT_GRACE_MS, default 200ms, cap 10s) — no client change needed for TS parity.

Review Notes

Independent review found 0 BLOCKERs, 2 WARNINGs, 4 INFOs. All addressed in commit cb65611a5:

  • W1: env-var test pollution — added explicit delete process.env.MCP_MESH_REGISTRY_URL to describe("postEvent") beforeEach so each test starts from a known state
  • W2: misleading source-of-truth comments on both TS and Python sides — updated to point at the actual NAPI/pyo3 wrapper remap functions (the contract source) rather than core's JobError::Display which is wrapper-overridden
  • INFO Update Examples to Use Published Packages #4: renamed getOrCreateProxy_getOrCreateProxy to match the existing _clearProxyCache underscore convention for test-only exports
  • Other INFOs (cause property assignment, payload normalization parity note, env-read-per-miss) are parity-faithful with Python or negligible — skipped per spec

Test plan

  • CI green across Rust, TypeScript, integration suite
  • Manual: a TS gateway calling mesh.jobs.postEvent(jobId, "extend", {by: 5}) reaches a Python producer's await job.recv_event(["extend"]) (cross-runtime sanity check)
  • Manual: a TS producer calling await job.recvEvent(["cancelled"]) exits gracefully when consumer calls proxy.cancel("reason") (200ms grace observed)

Closes #1042

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added job event posting and receiving capabilities to TypeScript and Python SDKs
    • New postEvent method to send events to running jobs
    • New recvEvent method to wait for and receive job events with optional type filtering
    • Support for event-driven job coordination and cancellation workflows
  • Tests

    • Added comprehensive integration test coverage for event injection, filtering, and cancellation scenarios

Review Change Stack

dhyansraj and others added 2 commits May 18, 2026 09:15
Closes #1042. TypeScript vertical for the consumer-pushed event
primitive that shipped Python-first in #1041 (closes #1032).

NAPI bindings (src/runtime/core/src/jobs_napi.rs):
  - JsJobController::recv_event(types, timeoutSecs) -> Promise<JobEvent|null>
  - JsJobProxy::send_event(eventType, payload) -> Promise<JobEventReceipt>
  - parse_timeout_secs helper guards NaN/Inf/negative; applied at
    both recv_event and existing wait() call sites
  - JobError::JobTerminal mapped to TS-visible named error

TS SDK (src/runtime/typescript/src/):
  - recvEvent / sendEvent surfaced on MeshJob interface in types.ts
  - New mesh.jobs namespace in jobs.ts with postEvent(jobId, type,
    payload) convenience helper; LRU-bounded JobProxy cache
    (default 256, env override MCP_MESH_JOBPROXY_CACHE_MAX) mirrors
    Python implementation. JS Map insertion-order semantics make
    O(1) LRU straightforward — no extra bookkeeping needed.
  - JobNotFoundError / JobTerminalError typed error classes
  - JobEvent / JobEventReceipt type definitions
  - Exports wired in index.ts (mesh.jobs namespace + types + errors)

Unit tests (src/runtime/typescript/src/__tests__/jobs.spec.ts):
  - +16 tests covering recv/send/postEvent, LRU eviction, typed-
    error mapping, NaN/Inf timeout guard, MCP_MESH_REGISTRY_URL
    discovery, cache keyed by (registryUrl, jobId)
  - Suite: 734 → 750 passing

Integration tests (tests/integration/suites/uc22_meshjob_ts/):
  - tc24_event_injection_happy_path_ts — payload round-trip
  - tc25_event_type_filter_ts — registry-side WHERE filter
  - tc26_cancel_posts_synthetic_event_ts — cancel-grace verified
    in TypeScript path
  - Reuses long-task-provider-ts / long-task-consumer-ts fixtures
    (extended with 3 new tools each)
  - Suite: 20 baseline + 3 new = 23/23 pass

API parity matrix (TS ↔ Python):

  recvEvent(types?, timeoutSecs?)     ↔ recv_event(types, timeout_secs)
  sendEvent(eventType, payload)       ↔ send_event(event_type, payload)
  mesh.jobs.postEvent(...)            ↔ mesh.jobs.post_event(...)
  JobNotFoundError                    ↔ JobNotFoundError
  JobTerminalError                    ↔ JobTerminalError
  MCP_MESH_JOBPROXY_CACHE_MAX (256)   ↔ MCP_MESH_JOBPROXY_CACHE_MAX (256)

Cancel synthetic event grace is server-side
(MCP_MESH_CANCEL_EVENT_GRACE_MS, default 200ms, cap 10s) — no
client change needed for TypeScript parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
W1: add explicit `delete process.env.MCP_MESH_REGISTRY_URL` to a
nested beforeEach in describe("postEvent") so each test starts from
a known env-var state. The previous arrangement worked by accident
of test ordering — fragile to test insertion or reordering.

W2: update source-of-truth comments on both TS and Python sides to
point at the actual contract source (NAPI/pyo3 wrapper remap
functions in jobs_napi.rs::job_error_to_napi and
jobs_py.rs::job_error_to_py) rather than the misleading reference
to core's JobError::Display. The wrapper remap deliberately
overrides Display with a stable SDK-facing format ("job is
terminal: ...") — a future maintainer thinking the remap is
redundant could silently break the substring contract on both
runtimes. Cite the actual line numbers (85-102 napi, 66-81 pyo3)
plus the specific JobTerminal arm.

I4: rename `getOrCreateProxy` → `_getOrCreateProxy` to match the
existing `_clearProxyCache` underscore convention for test-only
exports. 14 reference sites updated (1 export, 1 internal call, 12
in jobs.spec.ts). index.ts does NOT re-export — no public API
impact. dist/ stale references will regenerate on next build.

Tests: 750 passed, unchanged.

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

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@dhyansraj has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 24 minutes and 56 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02e2607f-b5d5-469a-a355-5d6d9a02668e

📥 Commits

Reviewing files that changed from the base of the PR and between cb65611 and aa1e944.

📒 Files selected for processing (2)
  • src/runtime/core/src/jobs_napi.rs
  • src/runtime/core/src/jobs_py.rs
📝 Walkthrough

Walkthrough

This PR implements TypeScript SDK parity for job event injection by extending N-API bindings with recv_event/send_event methods, defining MeshJob interface extensions and mesh.jobs namespace, providing comprehensive unit and integration test coverage, and demonstrating real-world event-driven patterns in test fixtures.

Changes

Job Event Injection for TypeScript SDK

Layer / File(s) Summary
N-API Binding Layer
src/runtime/core/src/jobs_napi.rs
Introduces parse_timeout_secs for JS timeout validation (rejects NaN/Infinity/negative), maps JobError::JobTerminal to stable error string, adds JSON helpers for JobEvent/JobEventReceipt serialization, and exposes JsJobController::recv_event and JsJobProxy::send_event methods. JsJobProxy::wait now validates timeout via the new helper.
TypeScript Type Surface and Exports
src/runtime/typescript/src/types.ts, src/runtime/typescript/src/index.ts
Extends MeshJob interface with optional recvEvent(types?, timeoutSecs?) and sendEvent(eventType, payload?) methods, defines MeshJobsNamespace with postEvent helper, and wires all exports (JobEvent, JobEventReceipt, error classes) through index.ts.
Unit Tests
src/runtime/typescript/src/__tests__/jobs.spec.ts
Mocks N-API JobController/JobProxy surface; covers error translation into JobTerminalError/JobNotFoundError, recv_event timeout/filter forwarding, postEvent proxy construction and LRU caching, and NaN/Infinity timeout rejection.
Integration Test Fixtures
tests/integration/suites/uc22_meshjob_ts/fixtures/long-task-provider-ts/src/index.ts, tests/integration/suites/uc22_meshjob_ts/fixtures/long-task-consumer-ts/src/index.ts
Provider tools (run_with_event, run_with_filter, run_until_cancel) wait for specific event types via job.recvEvent; consumer tools (commission_event, commission_event_filter, commission_cancel_via_event) post events via mesh.jobs.postEvent and orchestrate job lifecycle including cancellation.
Integration Test Scenarios
tests/integration/suites/uc22_meshjob_ts/tc24_event_injection_happy_path_ts/test.yaml, tests/integration/suites/uc22_meshjob_ts/tc25_event_type_filter_ts/test.yaml, tests/integration/suites/uc22_meshjob_ts/tc26_cancel_posts_synthetic_event_ts/test.yaml
End-to-end YAML test definitions: tc24 validates happy-path event injection with payload round-tripping, tc25 verifies event type filtering ignores non-matching types, tc26 confirms cancel posts synthetic "cancelled" event observable by recv_event.
Documentation Updates
src/runtime/python/mesh/jobs.py
Clarifies error documentation to reference stable substring contracts from Rust error remapping behavior, replacing outdated Display-based notes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • dhyansraj/mcp-mesh#885: Introduces the N-API JobController/JobProxy bindings in Rust; this PR extends those same bindings with new event methods and tighter timeout/error handling.

Poem

🐰 Hops with glee at event-driven tales,
N-API filters, validation prevails!
TypeScript and Rust in sync they dance,
Jobs now chat mid-flight, given a chance.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: implementing TypeScript SDK parity for MeshJob event injection with recvEvent/sendEvent/postEvent capabilities, which aligns with the core objective of adding bidirectional job event channels to the TypeScript SDK.
Linked Issues check ✅ Passed The PR comprehensively implements all coding requirements from issues #1042 and #1032: NAPI bindings (recv_event, send_event, parse_timeout_secs, JobTerminal error mapping), TypeScript SDK surface (recvEvent, sendEvent, postEvent, LRU cache, typed errors), type definitions, and unit/integration tests matching the public API parity matrix.
Out of Scope Changes check ✅ Passed All changes are scoped to the linked objectives: NAPI bindings, TypeScript SDK event injection APIs, LRU caching, error types, type definitions, unit tests, and integration test fixtures. No unrelated refactoring or modifications to unrelated components are present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/1042-ts-meshjob-event-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/core/src/jobs_napi.rs`:
- Around line 65-72: The parse_timeout_secs function currently uses
Duration::from_secs_f64 which can panic on very large finite inputs; change it
to use Duration::try_from_secs_f64 and convert the Result to napi::Result with
an appropriate Error::from_reason message (e.g., "timeoutSecs too large" plus
the value) so overflows return an Err instead of panicking. Also find the other
raw call to Duration::from_secs_f64(secs) in this file (the unprotected usage
noted) and replace it with Duration::try_from_secs_f64(secs).map_err(|e|
Error::from_reason(format!("timeoutSecs overflow: {}", e))) so both places
handle overflow safely and return napi errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d858348-73e4-4978-a38e-6812c4e2ba21

📥 Commits

Reviewing files that changed from the base of the PR and between 1758e28 and cb65611.

📒 Files selected for processing (11)
  • src/runtime/core/src/jobs_napi.rs
  • src/runtime/python/mesh/jobs.py
  • src/runtime/typescript/src/__tests__/jobs.spec.ts
  • src/runtime/typescript/src/index.ts
  • src/runtime/typescript/src/jobs.ts
  • src/runtime/typescript/src/types.ts
  • tests/integration/suites/uc22_meshjob_ts/fixtures/long-task-consumer-ts/src/index.ts
  • tests/integration/suites/uc22_meshjob_ts/fixtures/long-task-provider-ts/src/index.ts
  • tests/integration/suites/uc22_meshjob_ts/tc24_event_injection_happy_path_ts/test.yaml
  • tests/integration/suites/uc22_meshjob_ts/tc25_event_type_filter_ts/test.yaml
  • tests/integration/suites/uc22_meshjob_ts/tc26_cancel_posts_synthetic_event_ts/test.yaml

Comment thread src/runtime/core/src/jobs_napi.rs
…rror

Inline review on PR #1043 caught that parse_timeout_secs guarded
NaN/Inf/negative but still called the panic-prone
Duration::from_secs_f64. Very large finite inputs (e.g. f64::MAX
seconds) would panic inside the napi async runtime rather than
returning a clean JS Error rejection.

Both raw call sites in jobs_napi.rs fixed:
  - parse_timeout_secs (line 65) -> try_from_secs_f64 + Error::from_reason
    "timeoutSecs out of range: {e} (got {s})"
  - with_job_async_napi deadline_secs path (line 659) -> same pattern,
    error message includes job_id for diagnosis

Parity fix mirrored on jobs_py.rs:
  - parse_timeout_secs (line 45) -> try_from_secs_f64 + PyValueError
  - with_job_async_py (line 646) -> try_from_secs_f64 + inline NaN/Inf
    guard (the Python side lacked validate_deadline_secs equivalent)

Tests: +3 in jobs_napi.rs covers (a) valid passthrough, (b) NaN/Inf/
negative rejection, (c) f64::MAX overflow rejection. 415 -> 418
Rust lib tests pass on the typescript feature. Python-side parity
tests omitted because jobs_py.rs has no test mod (pyo3 extension-
module feature deliberately omits Python symbols at static-link
time); explanatory comment added pointing at the napi-side test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dhyansraj
dhyansraj merged commit a1470d0 into main May 18, 2026
20 checks passed
@dhyansraj
dhyansraj deleted the feature/1042-ts-meshjob-event-parity branch May 18, 2026 14:17
dhyansraj added a commit that referenced this pull request May 18, 2026
…1045)

## Summary

Closes #1044. Java vertical for the consumer-pushed event-injection
primitive shipped Python-first in #1041 and TypeScript in #1043.
**Completes the cross-runtime fan-out for issue #1032.**

Follows the original MeshJob shipping cadence: Python (#861/#878) →
TypeScript (#885) → Java (#891). This PR is the Java step.

## What's in the PR

### FFI bindings (`src/runtime/core/src/jobs_ffi.rs`)
- `mesh_job_controller_recv_event(handle, types_json, timeout_secs,
out_event_json)` → `i32` (0 ok, -1 invalid, -2 NotFound, -3 backend)
- `mesh_job_proxy_send_event(handle, event_type, payload_json,
out_receipt_json)` → `i32` (0 ok, -1 invalid, -2 NotFound, -3
**Terminal**, -4 backend) — distinct codes for `JobNotFound` vs
`JobTerminal` so the Java SDK can map to distinct typed exceptions
- `parse_ffi_timeout_secs` helper: rejects NaN/Inf, uses
`Duration::try_from_secs_f64` for overflow safety, negative-sentinel `<
0` convention for "no timeout" (since C ABI can't pass `Option<f64>`)
- +6 Rust FFI tests (jobs_ffi: 16 → 22)

### Java SDK (`src/runtime/java/mcp-mesh-sdk/`)
- `JobController.recvEvent(List<String> types, Duration timeout)` →
`Map<String, Object>` or null
- `JobProxy.sendEvent(String eventType, Map<String, Object> payload)` →
receipt Map
- New `MeshJobs.postEvent(jobId, eventType, payload)` static helper with
LRU-bounded `JobProxy` cache (default 256, env override
`MCP_MESH_JOBPROXY_CACHE_MAX`)
- Uses `LinkedHashMap(accessOrder=true)` with explicit `synchronized`
blocks — access-order mode mutates internal state on `get()` so
thread-safety requires external sync
- `removeEldestEntry` closes evicted proxies so FFI handles release
promptly
- `JobNotFoundException` + `JobTerminalException` typed exceptions
extending the existing `MeshException` (which extends `RuntimeException`
— unchecked, matches Python's `RuntimeError`-subclassing)
- mcp-mesh-core `MeshCore` JNR-FFI interface gets 2 new method
declarations

### Tests
- **Rust FFI**: +6 unit tests
- **Java unit**: +20 tests (`MeshJobsTest` 14, `JobEventApiTest` 6); sdk
module 42 → 62; full Java module 124 → 125
- **Integration suite `uc23_meshjob_java`**: 3 new test cases mirroring
Python tc24/25/26 and TS tc24/25/26_ts:
  - `tc24_event_injection_happy_path_java` — payload round-trip
  - `tc25_event_type_filter_java` — registry-side WHERE filter
- `tc26_cancel_posts_synthetic_event_java` — cancel-grace verified in
Java path
- Reuses existing `long-task-provider-java` / `long-task-consumer-java`
fixtures (extended with 3 new tools each)
  - Baseline 23 + new 3 = **26/26 pass**

### API parity matrix completed (3 runtimes)

| API | Java | TypeScript | Python |
|---|---|---|---|
| Producer wait for event | `job.recvEvent(types, duration)` |
`job.recvEvent(types, timeoutSecs)` | `job.recv_event(types,
timeout_secs)` |
| Consumer post to specific proxy | `proxy.sendEvent(type, payload)` |
`proxy.sendEvent(type, payload)` | `proxy.send_event(type, payload)` |
| Convenience helper | `MeshJobs.postEvent(...)` |
`mesh.jobs.postEvent(...)` | `mesh.jobs.post_event(...)` |
| Typed errors | `JobNotFoundException` / `JobTerminalException` |
`JobNotFoundError` / `JobTerminalError` | `JobNotFoundError` /
`JobTerminalError` |
| Proxy cache cap | `MCP_MESH_JOBPROXY_CACHE_MAX` (256) | same | same |
| Cancel synthetic event grace | Server-side
`MCP_MESH_CANCEL_EVENT_GRACE_MS` (200ms default, 10s cap) | same | same
|

## Review Notes

Independent review returned **0 BLOCKERs, 0 WARNINGs, 4 INFOs.** All 4
addressed in commit `99da6a504`:

- **I1**: `JobNotFoundException` javadoc had
`JobError::Other(BackendError::NotFound)` — actual variant is
`JobError::Backend(BackendError::NotFound)`. Typo fixed.
- **I2**: Added a 7-line policy comment above `parse_ffi_timeout_secs`
documenting why two timeout policies coexist (strict for new surfaces;
permissive for back-compat on `mesh_job_proxy_wait`).
- **I3**: Added a parallel 6-line lock-rationale comment on
`JobController.recvEvent`'s synchronized block, modeled on
`JobProxy.await()`'s existing doc — explains the use-after-free fence
(concurrent `close()` must not free the FFI handle mid-poll).
- **I4**: Added direct LRU eviction test
`getOrCreateProxy_evictsLruAndClosesProxyAtCap` exercising the
`removeEldestEntry → JobProxy.close() → mesh_job_proxy_free` path with
the default cap (256 + 1 overflow). 257 FFI allocations in 24ms —
acceptable cost for a regression guard on the resource-cleanup
guarantee.

## What's still open for #1032 epic

After this PR merges:
- ✅ Python (#1041)
- ✅ TypeScript (#1043)
- ✅ Java (#1044, this PR)
- ⏳ `Stream[T]` subscription mode (the paired feature originally cited
in #1032 — depends on reading #645's current Stream[T] producer model)

## Test plan

- [ ] CI green across Rust, Java, integration suite
- [ ] Manual: cross-runtime sanity (Python `mesh.jobs.post_event(jobId,
...)` reaches Java `await job.recvEvent(["..."], ...)` — and vice versa)
- [ ] Manual: a Java producer with `await job.recvEvent(["cancelled"])`
exits gracefully when consumer (any runtime) calls
`proxy.cancel("reason")` (200ms grace observed)

Closes #1044

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

# Release Notes

## New Features
- Added job event communication: send events via `sendEvent()` and
receive events via `recvEvent()` with optional timeout support
- Event type filtering to selectively consume specific event types while
ignoring others
- New `MeshJobs` utility class for posting events without maintaining
proxy instances
- New typed exceptions for better error handling: `JobNotFoundException`
and `JobTerminalException`

## Tests
- Added integration tests validating event injection, type filtering,
and event-based cancellation workflows

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1045?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dhyansraj added a commit that referenced this pull request May 18, 2026
## Summary

- Adds **Stream[T] subscription mode** for MeshJob (Python vertical of
#1032 follow-up): `mesh.jobs.subscribe_events(job_id, types=, after=,
long_poll_secs=) -> AsyncIterator[dict]`.
- Counterpart to the point-to-point `recv_event`/`send_event` surface
(#1041). Subscription is non-destructive — multiple subscribers can
observe the same job's event log independently, each with its own
per-call cursor.
- Three layers: Rust core `JobProxy::list_events` (thin pass-through to
`backend.list_job_events`), pyo3 binding `PyJobProxy.list_events` with
`parse_timeout_secs` guard, Python SDK `mesh.jobs.subscribe_events`
async generator.
- Integration test tc27 extends existing fixtures with `run_until_done`
(producer) and `commission_subscribe_observer` (consumer) tools — same
job is observed by both `recv_event` (consumer) and `subscribe_events`
(third-party observer), proving cursor independence.

## Review Notes

Independent review caught 1 blocker + 2 substantive warnings (all
addressed in the amended commit):

1. **Public `long_poll_secs` surface**: now `Optional[float] = 30.0`
(was `float = 30.0`). `None` reaches the binding's "single immediate
read" path; previously unreachable from Python. Unit test added.
2. **Cursor advance + `seq` validation**: cursor now advances **before**
yield, and `event["seq"]` is type-validated with a clear `RuntimeError`
instead of a half-yielded `KeyError`. Unit test added.
3. **YAML assertion robustness**: replaced the whitespace-sensitive
`"posted_seqs":[1,2,3]` substring with structured per-seq checks
tolerant to encoder spacing changes.

Other review WARNINGs (`async def`+`yield` annotation footgun in
docstring, subscriber-vs-poster scheduling race in the fixture) and the
5 INFOs are deferred — none affect correctness.

Closes #1046

Sibling verticals (TS, Java) of this same subscription mode will follow
as separate PRs to keep the diffs reviewable, mirroring the #1041#1043#1045 cadence.

## Test plan

- [x] Python unit tests pass (`test_meshjob_events.py`: 27 tests, +5 new
in this PR — 2 from fixes, 3 from initial implementation)
- [x] Rust unit tests pass (`jobs.rs`: +6 new for
`JobProxy::list_events`)
- [x] uc21_meshjob integration suite: 21/21 pass (20 baseline + 1 new
tc27 in 193.8s)
- [x] `parse_timeout_secs` guard reused from #1041 (NaN/Inf/negative
reject; `try_from_secs_f64` overflow)
- [x] LRU `JobProxy` cache reused from #1041 (no duplicate cache)
- [x] Server-side `types` filter forwarded all the way to
`backend.list_job_events` (no client-side post-filter)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Job event listing: Query events with optional type filtering and
long-polling support for efficient batch retrieval without affecting
internal state
* Job event subscription: Subscribe to event streams with automatic
cursor management, enabling asynchronous observation of job activity
across multiple batches
  
* **Tests**
* Integration tests validating concurrent event publishing and
concurrent subscription via observer pattern

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1047?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
dhyansraj added a commit that referenced this pull request May 18, 2026
## Summary

- Adds **Stream subscription mode** for MeshJob (TypeScript vertical of
#1032 follow-up): `mesh.jobs.subscribeEvents(jobId, { types, after,
longPollSecs }) -> AsyncGenerator<JobEvent>`.
- Mirrors the Python vertical (#1047). Counterpart to the point-to-point
`recvEvent`/`postEvent` surface shipped in #1043. Multiple subscribers
can observe the same job's events independently via per-call cursors.
- Three layers: napi-rs binding `JsJobProxy::list_events` returning
`ListEventsResult { events, next_after }` (napi-rs v3 doesn't
auto-serialize bare tuples), TS SDK `subscribeEvents` async generator
reusing the existing `_getOrCreateProxy` LRU cache, integration test
tc27 in `uc22_meshjob_ts` extending the long-task fixtures with
`run_until_done` (producer) and `commission_subscribe_observer`
(consumer).
- Side benefit: scrubs a stray null byte from `jobs.ts:187`
(`${registryUrl}\x00${jobId}` → `${registryUrl} ${jobId}`) inherited
from an earlier PR that made git classify the file as binary in diffs.
This also explains the `Bin -> Bin` line in the file stat — `git diff
-a` shows the actual textual diff.

## Review Notes

Independent review caught 4 substantive WARNINGs (all addressed in the
amended commit):

1. **`bigint` → `number` cursor truncation**: TSDoc accepted `bigint`
for i64-sourced cursors, but `Number(after)` silently truncated values
above `2^53`. Now guarded with an explicit `RangeError` when `after >
BigInt(Number.MAX_SAFE_INTEGER)`.
2. **Missing "no `seq` key" malformed-payload test**: Python sibling had
this test; TS now has parity (`rejects event without seq key`).
3. **Subscriber drain comment misleading**: `.catch(() => {})` only
suppresses the unhandled rejection — it does NOT cancel the underlying
long-poll (JS has no cancellation primitive without `AbortController`
plumbed through napi). Comment rewritten to be honest. Plumbing
`AbortController` through napi is a known limitation left out of scope.
4. **LRU-reuse test asserted caching only**: now strengthened to assert
both iters observe `seq=1` independently — the actual headline contract.

Other findings (3 INFOs) skipped as cosmetic or already-intentional
patterns.

Closes #1048

Sibling Java vertical of subscription mode will follow as a separate PR,
mirroring the #1043#1045 cadence.

## Test plan

- [x] TS unit tests pass (`jobs.spec.ts`: 23 tests, +7 new in this PR —
6 from initial implementation + 1 from review fix)
- [x] napi binding rebuild + `src-tests` 12/12 pass
- [x] uc22_meshjob_ts integration suite: 24/24 pass (23 baseline + 1 new
tc27 in 54.4s)
- [x] `parse_timeout_secs` guard reused at napi boundary
(NaN/Inf/negative reject)
- [x] LRU `JobProxy` cache reused from #1043 (no duplicate cache)
- [x] Server-side `types` filter forwarded all the way to the registry
(no client-side post-filter)
- [x] `nextAfter` watermark advances cursor on empty pages (parity with
Python #1047)
- [x] Bool-`seq` and missing-`seq` rejection (parity with Python
`RuntimeError` shape)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added `subscribeEvents()` function to the jobs namespace for real-time
event subscription with optional event type filtering and long-polling
support
* Events support cursor-based pagination for reliable delivery across
subscription sessions

* **Tests**
* Added comprehensive integration tests validating concurrent event
subscription alongside event consumption patterns

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1049?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
dhyansraj added a commit that referenced this pull request May 19, 2026
…1053)

## Summary

Documentation-only PR covering the six commits shipped since v2.1.0
(MeshJob event-injection trilogy: #1041 / #1043 / #1045 for `recv_event`
/ `send_event` / `post_event`; #1047 / #1049 / #1051 for
`subscribe_events`).

- `docs/concepts/jobs.md` — adds two new sections after the existing
v2.0 baseline material: **Event injection** (producer/consumer model +
recv loop + post pattern + type filtering + synthetic cancel event with
grace window + typed errors) and **Stream subscription** (per-call
cursor + `next_after` watermark + no-automatic-terminal-detection +
multiple-concurrent-subscribers semantics). Both sections carry
cross-runtime Python/TS/Java tabbed examples respecting the three
distinct iterator shapes (Python async generator, TS async generator,
Java blocking `Closeable` iterator).
- `docs/concepts/stateful-agents.md` — replaces the stale "Coming soon:
mesh-managed event channel" block (which referenced #1032 as roadmap)
with an accurate cross-link to the new sections.
- `docs/concepts/index.md` — grid card teaser mentions event injection.
- `docs/environment-variables.md` — documents
`MCP_MESH_JOBPROXY_CACHE_MAX` (default 256) and
`MCP_MESH_CANCEL_EVENT_GRACE_MS` (default 200, capped 10000).
- `src/core/cli/man/content/` — same env-var content mirrored to
`environment.md`; new **Event injection** + **Stream subscription**
sections added to `jobs.md`, `jobs_typescript.md`, `jobs_java.md` (each
variant carries idiomatic native-runtime code).
- `RELEASE_NOTES.md` — `## v2.2.0 (UNRELEASED)` stub at the top,
grouping the six commits by feature rather than PR number, mirroring the
v2.1.0 entry's voice and structure.

`meshctl man` content is **hand-maintained markdown**, not generated
from `docs/` — both surfaces were updated to keep them in sync.

## Review Notes

Independent review caught 3 substantive WARNINGs (all addressed) and
surfaced one pre-existing adjacent inaccuracy (also folded in):

1. **Java `Map.of` foot-gun** — the cancel-event handling example used
`Map.of("reason", payloadMap.get("reason"))`, which NPEs if the
synthetic cancel event has no `reason` field (the API allows null
reasons). Replaced with a null-tolerant pattern using
`Objects.toString(payload.get("reason"), "")`.
2. **Misstated Java cancel mechanism** — `jobs_java.md` claimed handlers
observe cancel via `InterruptedException`. Per
`JobController.java:239-249`, Java's `Thread#sleep` cannot be
interrupted by the Tokio cancel token firing — handlers must poll
`isCancelled()` between work units OR park on `recvEvent(["cancelled",
...])`. Rewrote the section accurately.
3. **TS double-bang + `as any` cast** — the canonical TS doc example
used `job!.recvEvent!(...)` and `(event.payload as any)?.reason`.
Replaced with idiomatic early-return narrowing + `Record<string,
unknown>` payload typing so the example doesn't teach non-idiomatic TS.
4. **Adjacent pre-existing inaccuracy** — `jobs_java.md:308-310`
(outside the diff) carried the same wrong `Thread.sleep` interrupt claim
in the Cancellation section. Folded a one-paragraph fix in since the
same surface area was already being edited.

All API signatures, env-var defaults (256, 200ms, 10000ms cap), tab
indentation, sequence-diagram syntax, anchor links, and framing verified
accurate against source files.

Closes #1052

A follow-up PR (tracked separately) will add a runnable example agent
under `examples/` demonstrating the same APIs.

## Test plan

- [x] `mkdocs build` clean (no new errors/warnings introduced;
pre-existing orphan-page warnings unrelated)
- [x] `mkdocs serve` renders new sections cleanly (grid cards, tabbed
code, mermaid `sequenceDiagram` blocks)
- [x] `meshctl man environment --raw | grep -E
'MCP_MESH_(JOBPROXY|CANCEL_EVENT)'` matches both env vars
- [x] `meshctl man jobs [--typescript|--java] --raw` matches new Event
injection + Stream subscription sections per runtime
- [x] `meshctl man jobs --java --raw | grep InterruptedException`
returns only an unrelated `throws` clause in a code sample — the wrong
cancel-mechanism claim is gone
- [x] `meshctl man jobs --typescript --raw | grep -E '!\.|\bas any\b'`
zero matches — non-idiomatic TS scrubbed
- [x] `go build ./...` clean (man content compiles via `//go:embed`)
- [x] No-deprecated-in-docs rule observed; public-artifact framing
observed (no naming of downstream consumers, no behavioral framing)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* MeshJob event injection: post and receive job events across all
supported runtimes with typed error handling and configurable
cancel-event grace window.
* MeshJob stream subscription: non-destructive event observation with
independent per-call cursors and watermark filtering.

* **Documentation**
* Added comprehensive event injection and stream subscription guidance
across Python, TypeScript, and Java documentation.
  * Documented new environment variables for event channel tuning.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1053?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
dhyansraj added a commit that referenced this pull request May 19, 2026
## Summary

Release v2.2.0 — the MeshJob event-injection wave.

Rolls up six feature PRs (cross-runtime point-to-point event injection +
stream subscription) plus three docs/examples PRs into a stable release:

- **MeshJob event injection** — `recv_event` / `send_event` /
`post_event` across Python (#1041), TypeScript (#1043), Java (#1045).
- **MeshJob stream subscription** — `subscribe_events` async generator
(Python #1047, TS #1049) and `EventSubscription` Closeable iterator
(Java #1051).
- **Docs + examples** — concepts + env vars + meshctl man + release
notes (#1053), signature-drift follow-up (#1056), runnable
event-injection example agents across all three runtimes (#1058).

See `RELEASE_NOTES.md` for the per-feature narrative grouping all six
feature PRs.

## Mechanical changes in this PR

- `scripts/bump_version.py 2.1.0 2.2.0` — 417 files updated across 36
categories (Cargo manifests, pyproject.toml, package.json, helm charts,
scaffold templates, man content, test config, release workflow
versions).
- `helm dependency update helm/mcp-mesh-core` — Chart.lock regenerated.
- `cargo generate-lockfile` (src/runtime/core) — Cargo.lock refreshed.
- `RELEASE_NOTES.md` — `## v2.2.0 (UNRELEASED)` → `## v2.2.0
(2026-05-19)`, top-of-file Full Changelog link bumped to
`v2.2.0...HEAD`, new `v2.1.0...v2.2.0` link added above the v2.2.0
heading.

Net diff: 398 files changed, +613 / -611. Mostly one-line version bumps.

Closes #1059

## Test plan

- [x] Dry-run `bump_version.py` matched expected scope (537 files / 36
categories)
- [x] No stray `2.1.0` mcp-mesh-internal references left after bump
(sanity grep — only transitive npm-dep matches remain)
- [x] `helm dependency update` + `cargo generate-lockfile` reminders
followed
- [x] All upstream feature PRs (#1041#1058) merged and validated
end-to-end before this release was cut
- [ ] **After merge**: tag `v2.2.0` pushed to origin (HOLD for user
inspection of merged main)
- [ ] **After tag**: publish workflow fired (HOLD for explicit user
go-ahead)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
dhyansraj added a commit that referenced this pull request May 22, 2026
## Summary

TypeScript parity for the Python `mesh.jobs.cancel/status/wait` facades
shipped in #1077. Three new module-level async functions mirror the
existing `postEvent` / `subscribeEvents` DDDI-clean pattern — callers
with only a `jobId` no longer need to construct `new JobProxy(jobId,
registryUrl)` and pass `MCP_MESH_REGISTRY_URL` explicitly.

```ts
export async function cancel(jobId: string, reason?: string): Promise<void>
export async function status(jobId: string): Promise<JobStatus>
export async function wait(jobId: string, timeoutSecs?: number): Promise<unknown>
```

Each: `resolveRegistryUrl()` → `_getOrCreateProxy()` (LRU-cached) → napi
dispatch → `translateJobError()` for typed exception re-classification.
Underlying napi binding (`JsJobProxy.cancel/status/wait`) was already
exposed — **no Rust changes**.

`JobStatus` interface added alongside `JobEvent` / `JobEventReceipt`,
exported from `mesh.jobs`. Fields mirror `job_to_json` in
`jobs_napi.rs`: required fields typed `T`, `Option<T>` fields emitted as
`T | null` (every key always present; only null-checks needed, no
key-presence checks).

`MeshJobsNamespace` extended with `cancel`/`status`/`wait`
alphabetically; public exports added.

## Bonus generalization

`resolveRegistryUrl`'s error message prefix generalized from
`"mesh.jobs.postEvent:"` → `"mesh.jobs:"`. Now accurate for all four
facades — mirrors the same generalization the Python BLOCKER fix made in
#1077.

## Out of scope

- **Java parity** — separate PR per polyglot cadence (mirrors the #1041#1043#1045 trilogy pattern)
- **`TimeoutError` typed class** — `wait()` currently rejects with plain
`Error` whose message starts with `"timeout:"`. Substring contract
pinned by test. Follow-up if needed.
- **Caller authorization** — design discussion at #1076

## Review Notes

Independent review: 0 BLOCKER, 1 WARNING (addressed in amended commit),
5 INFOs (skipped as cosmetic).

- **WARNING fixed**: `JobStatus` interface used `field?: T` for nullable
fields, but `job_to_json` always emits every field with `Value::Null`
fallback. Tightened to `field: T | null` where appropriate, and `field:
T` (no optional, no null) for fields the Rust binding emits
unconditionally. Added one test pinning the every-key-present contract
via `Object.keys` comparison.

INFOs skipped: `Promise<unknown>` vs `Any` (the stricter shape is
correct), repeated try/catch boilerplate matches `postEvent`'s style,
empty `toHaveBeenCalledWith()` matcher is intentional for nullary calls,
JSDoc null-vs-undefined nuance, doc table layout.

## Test plan

- [x] TS unit tests: 39 pass (was 23, +16 new — 15 facade tests + 1
contract test)
- [x] uc22_meshjob_ts integration: 24/24 pass (222.8s)
- [x] `npm run build` clean (no TS compile errors after type tightening)
- [x] Vitest hoisting workaround confined to test file (`import { cancel
as cancelFacade }`); SDK exports stay canonical — `import { cancel }
from "@mcpmesh/sdk"` works unaliased for end users
- [x] Cross-runtime scope: no edits to Python / Java / Rust /
`jobs_java.md` / Python `jobs.md` variant
- [x] Public-artifact framing: no naming downstream consumers;
structural language throughout

Closes #1078


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Added job lifecycle management capabilities: cancel jobs, retrieve job
status, and wait for job completion.
* Enhanced job operation documentation with TypeScript implementation
examples and per-runtime behavior specifications.

* **Tests**
* Expanded test coverage for new job control operations and error
handling scenarios.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/dhyansraj/mcp-mesh/pull/1079?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant