Skip to content

NexusSerializationContext for data/failure converters - #1828

Open
JoshuaFrenchwood wants to merge 5 commits into
mainfrom
adding-nexus-serialization-context
Open

NexusSerializationContext for data/failure converters#1828
JoshuaFrenchwood wants to merge 5 commits into
mainfrom
adding-nexus-serialization-context

Conversation

@JoshuaFrenchwood

Copy link
Copy Markdown
Contributor

What was changed

Added NexusSerializationContext for nexus callers and sync handlers. This allows data and failure converters to use nexus endpoint, service and operation to be used for encoding and decoding.

Why?

This enables codecs to select serialization behavior or encryption keys by Nexus endpoint, service, or operation.

For example, workflows calling two Nexus endpoints can encrypt each endpoint’s payloads with a different key while ensuring that inputs, results, and failures are decoded with the converter selected for the corresponding operation.

Checklist

  1. Closes

  2. How was this tested:

Added unit/functional tests to verify that NexusSerializationContext works as expected.

  1. Any docs updates needed?

@JoshuaFrenchwood
JoshuaFrenchwood force-pushed the adding-nexus-serialization-context branch from f36df7a to 0774bf3 Compare September 9, 2026 20:09
@JoshuaFrenchwood
JoshuaFrenchwood marked this pull request as ready for review September 9, 2026 20:09
@JoshuaFrenchwood
JoshuaFrenchwood requested a review from a team as a code owner September 9, 2026 20:09
@JoshuaFrenchwood
JoshuaFrenchwood force-pushed the adding-nexus-serialization-context branch from 0774bf3 to 21ee938 Compare September 9, 2026 20:16
Comment thread temporalio/worker/_workflow_instance.py Outdated
Comment thread temporalio/worker/_workflow_instance.py Outdated
self._workflow_context_failure_converter,
)
summary_payload_converter = payload_converter
failure_converter = self._context_free_failure_converter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should have a todo here. @VegetarianOrc already needs to extend this for worker callbacks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO for this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Worker callbacks are only delivered as Nexus tasks, and won't affect start nexus operation commands from a workflow.

Aside from that, is there a reason why we aren't using user_failure_converter here?

Comment thread temporalio/worker/_nexus.py Outdated
Comment thread temporalio/client/_interceptor.py Outdated
Comment thread temporalio/client/_impl.py
@JoshuaFrenchwood
JoshuaFrenchwood force-pushed the adding-nexus-serialization-context branch 3 times, most recently from c91cfa3 to b98ea57 Compare September 11, 2026 15:02
@JoshuaFrenchwood
JoshuaFrenchwood force-pushed the adding-nexus-serialization-context branch from b98ea57 to 117a7fd Compare September 11, 2026 18:30
run_id: str | None
rpc_metadata: Mapping[str, str | bytes]
rpc_timeout: timedelta | None
result_type: type[Any] | None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This and the below are breaking changes that would need to be called out explicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added this back

Comment thread temporalio/client/_interceptor.py Outdated
async def get_nexus_operation_result(
self, input: GetNexusOperationResultInput
) -> Any:
) -> temporalio.api.workflowservice.v1.PollNexusOperationExecutionResponse:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This appears to be the only proto being exposed in the whole interceptor, it probably shouldn't be.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like the most similar thing to do like other ones would be a new object like WorkerTaskReachability or NexusOperationExecutionDescription, the latter of which similarly contains a raw proto and a dataconverter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Created a new object called GetNexusOperationResultOutput for this. It includes raw_result, raw_failure, and data_converter.

Comment thread temporalio/client/_impl.py Outdated
)
return await NexusOperationExecutionDescription._from_execution_info(
info=resp.info,
data_converter=self._client.data_converter,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason that this dataconverter doesn't get the serialization context?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated, before operation wasnt a field in GetNexusOperationResultInput, but since I added that it can create the serialization context

Comment thread temporalio/client/_nexus.py Outdated
cls,
info: temporalio.api.nexus.v1.NexusOperationExecutionInfo,
data_converter: temporalio.converter.DataConverter,
failure_data_converter: temporalio.converter.DataConverter | None = None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure this is necessary. I think data_converter should get the context and be used for failure conversion and elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed that, thanks

Comment thread temporalio/client/_nexus.py Outdated
result_type: type | None = None,
endpoint: str = "",
service: str = "",
_nexus_serialization_context: (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
_nexus_serialization_context: (
nexus_serialization_context: (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed this field

Comment thread temporalio/client/_interceptor.py Outdated
Comment on lines +662 to +680
_data_converter: DataConverter = field(repr=False, compare=False)


@dataclass
class GetNexusOperationResultOutput:
"""Output for :py:meth:`OutboundInterceptor.get_nexus_operation_result`.

.. warning::
This API is experimental and unstable.
"""

raw_result: temporalio.api.common.v1.Payload | None
"""Raw result payload if the operation succeeded."""

raw_failure: temporalio.api.failure.v1.Failure | None
"""Raw failure if the operation failed."""

data_converter: DataConverter = field(repr=False, compare=False)
"""Data converter for decoding the result or failure."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think we should consider adding endpoint, service and operation to GetNexusOperationResultInput. Then the serialization context can be created in the terminal interceptor and decoding can happen as it did previously.

Threading the data converter or context itself through and having this output type feels a bit off to me. Happy to discuss though!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I like that approach, I updated it to have all three, endpoint/service/operation.

Comment on lines +109 to +111
The context is not propagated to the eventual result of an asynchronous operation. Standalone
operation handles use the context of their start request, including when an existing operation
is returned, while handles created without starting an operation do not receive it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The first sentence here seems to belong with the section above and the part about standalone operation handles is a little hard to parse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Update this doc, tried to copy over what we had in the go-sdk

Comment on lines +113 to +114
Callers and handlers receive this context on opposite sides of failure conversion. Contextual
encodings should therefore be self-describing and support legacy payloads without context.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe I'm just not seeing it, but I'm not sure what "opposite sides of failure conversion" means exactly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated

Comment thread temporalio/worker/_nexus.py Outdated
Comment on lines +422 to +424
data_converter = data_converter or self._data_converter_for_nexus_task(
endpoint, start_request.service, start_request.operation
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO we should either make this always use self._data_converter_for_nexus_task and remove the argument or make the argument required.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Made it required.

Comment thread temporalio/worker/_workflow_instance.py Outdated
self._workflow_context_failure_converter,
)
summary_payload_converter = payload_converter
failure_converter = self._context_free_failure_converter

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Worker callbacks are only delivered as Nexus tasks, and won't affect start nexus operation commands from a workflow.

Aside from that, is there a reason why we aren't using user_failure_converter here?

Comment thread tests/test_serialization_context.py Outdated
Comment on lines +1729 to +1731
HMAC_ENCODING = b"binary/nexus-context-hmac"
ZLIB_ENCODING = b"binary/nexus-context-zlib"
HMAC_KEY = b"nexus-context-test-key"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Smallish nit: Seems like we could probably prove the context usage w/o using these various encodings and using a simpler marker or something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated this to be a simpler red/blue test for two different nexus endpoint

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.

3 participants