-
Notifications
You must be signed in to change notification settings - Fork 266
Add Client API Cell protocol vocabulary (client/cell/defs.py) #4856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
YuanTingHsieh
wants to merge
4
commits into
NVIDIA:main
Choose a base branch
from
YuanTingHsieh:yuantingh/client-api-te1-protocol-auth
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
bbf1202
Add Client API Cell protocol vocabulary and session-token auth primit…
YuanTingHsieh a5d60ea
TE-1: trim to stateless proof toolkit; defer SessionTokenManager to a…
YuanTingHsieh bdb520a
TE-1: address review — proof length guard, entropy floors, HELLO_REJE…
YuanTingHsieh 4bd2d09
TE-1: reduce freeze to the protocol vocabulary; drop auth mechanism
YuanTingHsieh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| """Protocol vocabulary for the Client API control protocol over Cell. | ||
|
|
||
| This module is interface freeze #1 of the Client API Execution Modes design | ||
| (docs/design/client_api_execution_modes.md, "Control Protocol"). It is consumed by the | ||
| trainer-side Cell engine, the external_process backend, and the attach backend. It is a | ||
| pure vocabulary module: constants only, no Cell/cellnet imports, no I/O. | ||
| """ | ||
|
|
||
| # Cell channel used for all Client API control protocol messages. | ||
| # Clearly namespaced so it cannot collide with the legacy FlareAgent channel | ||
| # ("flare_agent" in nvflare/client/ipc/defs.py) or other Cell channels. | ||
| # This is a frozen wire constant; its exact value is part of the cross-track contract. | ||
| CHANNEL = "client_api" | ||
|
|
||
| # The Client API control protocol version carried in HELLO. | ||
| # V1 supports exactly one protocol version; the field exists so later versions | ||
| # can define a compatibility window. | ||
| PROTOCOL_VERSION = 1 | ||
|
|
||
|
|
||
| class Topic: | ||
| """Topics of the Client API control protocol messages over the Cell CHANNEL. | ||
|
|
||
| Reply-type messages (HELLO_CHALLENGE, HELLO_ACCEPTED, TASK_ACCEPTED, RESULT_ACCEPTED, | ||
| RESULT_REJECTED) may be modeled as Cell request replies at runtime rather than separate | ||
| sends; the constants still name them so state machines, logs, and tests share one | ||
| vocabulary. | ||
|
|
||
| Values are prefixed with "client_api." so they never collide with legacy topic values | ||
| (e.g. "hello"/"heartbeat"/"abort"/"bye" in nvflare/client/ipc/defs.py). | ||
| """ | ||
|
|
||
| # Session setup (all out-of-process modes) | ||
| HELLO = "client_api.hello" | ||
| HELLO_CHALLENGE = "client_api.hello_challenge" | ||
| HELLO_PROOF = "client_api.hello_proof" | ||
| HELLO_ACCEPTED = "client_api.hello_accepted" | ||
| # Distinct from ERROR (a protocol/transport error): HELLO_REJECTED is a clean, semantic | ||
| # auth/handshake refusal (bad proof, wrong scope, consumed/expired nonce, single-session), | ||
| # so TE-3/AT-2 state machines can tell a recoverable auth failure from a protocol fault. | ||
| HELLO_REJECTED = "client_api.hello_rejected" | ||
|
|
||
| # Per task (every round) | ||
| TASK_READY = "client_api.task_ready" | ||
| TASK_ACCEPTED = "client_api.task_accepted" | ||
| TASK_FAILED = "client_api.task_failed" | ||
| RESULT_READY = "client_api.result_ready" | ||
| RESULT_ACCEPTED = "client_api.result_accepted" | ||
| RESULT_REJECTED = "client_api.result_rejected" | ||
|
|
||
| # Throughout the session | ||
| LOG = "client_api.log" | ||
| HEARTBEAT = "client_api.heartbeat" | ||
|
|
||
| # Teardown / failure | ||
| ABORT = "client_api.abort" | ||
| SHUTDOWN = "client_api.shutdown" | ||
| BYE = "client_api.bye" | ||
| ERROR = "client_api.error" | ||
|
|
||
|
|
||
| class MsgKey: | ||
| """Keys for Client API control protocol message payloads. | ||
|
|
||
| These string values are the frozen wire contract shared across tracks; renaming a value is | ||
| a protocol break, not a refactor. | ||
| """ | ||
|
|
||
| SESSION_ID = "session_id" | ||
| ATTACH_ID = "attach_id" | ||
| JOB_ID = "job_id" | ||
| SITE_NAME = "site_name" | ||
| TRAINER_FQCN = "trainer_fqcn" | ||
| TARGET_FQCN = "target_fqcn" | ||
| RANK = "rank" | ||
| # Rank policy the session is scoped to (a TokenScope / HELLO_PROOF-covered field); | ||
| # distinct from RANK, which is the concrete rank a trainer reports in HELLO. | ||
| RANK_POLICY = "rank_policy" | ||
| PROTOCOL_VERSION = "protocol_version" | ||
| NONCE = "nonce" | ||
| PROOF = "proof" | ||
| REASON = "reason" | ||
| TASK_ID = "task_id" | ||
| # TASK_READY carries the task name alongside the task id. | ||
| TASK_NAME = "task_name" | ||
| # TASK_READY carries the FLModel reference and its params (lazy refs, not materialized bytes). | ||
| MODEL = "model" | ||
| PARAMS = "params" | ||
| RESULT_ID = "result_id" | ||
| TRANSFER_ID = "transfer_id" | ||
| # RESULT_READY carries the payload manifest describing the result envelope. | ||
| MANIFEST = "manifest" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from nvflare.client.cell.defs import CHANNEL, PROTOCOL_VERSION, MsgKey, Topic | ||
| from nvflare.client.ipc import defs as ipc_defs | ||
|
|
||
|
|
||
| def _public_str_values(clazz) -> dict: | ||
| return {name: value for name, value in vars(clazz).items() if not name.startswith("_") and isinstance(value, str)} | ||
|
|
||
|
|
||
| class TestDefs: | ||
| def test_protocol_version(self): | ||
| assert PROTOCOL_VERSION == 1 | ||
|
|
||
| def test_expected_topics_present(self): | ||
| expected = { | ||
| "HELLO", | ||
| "HELLO_CHALLENGE", | ||
| "HELLO_PROOF", | ||
| "HELLO_ACCEPTED", | ||
| "HELLO_REJECTED", | ||
| "TASK_READY", | ||
| "TASK_ACCEPTED", | ||
| "TASK_FAILED", | ||
| "RESULT_READY", | ||
| "RESULT_ACCEPTED", | ||
| "RESULT_REJECTED", | ||
| "LOG", | ||
| "HEARTBEAT", | ||
| "ABORT", | ||
| "SHUTDOWN", | ||
| "BYE", | ||
| "ERROR", | ||
| } | ||
| assert expected == set(_public_str_values(Topic).keys()) | ||
|
|
||
| def test_topic_values_unique(self): | ||
| values = list(_public_str_values(Topic).values()) | ||
| assert len(values) == len(set(values)) | ||
|
|
||
| def test_msg_key_values_unique(self): | ||
| values = list(_public_str_values(MsgKey).values()) | ||
| assert len(values) == len(set(values)) | ||
|
|
||
| def test_no_collision_with_legacy_ipc_defs(self): | ||
| # the Client API control protocol must not collide with the legacy FlareAgent | ||
| # channel/topic values in nvflare/client/ipc/defs.py | ||
| assert CHANNEL != ipc_defs.CHANNEL | ||
| legacy_topics = { | ||
| value for name, value in vars(ipc_defs).items() if name.startswith("TOPIC_") and isinstance(value, str) | ||
| } | ||
| assert not legacy_topics.intersection(_public_str_values(Topic).values()) | ||
|
|
||
| def test_channel_wire_value(self): | ||
| # frozen cross-track wire constant: renaming this value is a protocol break | ||
| assert CHANNEL == "client_api" | ||
|
|
||
| def test_topic_wire_values(self): | ||
| # exact frozen wire strings for every topic; a value rename must fail CI | ||
| expected = { | ||
| "HELLO": "client_api.hello", | ||
| "HELLO_CHALLENGE": "client_api.hello_challenge", | ||
| "HELLO_PROOF": "client_api.hello_proof", | ||
| "HELLO_ACCEPTED": "client_api.hello_accepted", | ||
| "HELLO_REJECTED": "client_api.hello_rejected", | ||
| "TASK_READY": "client_api.task_ready", | ||
| "TASK_ACCEPTED": "client_api.task_accepted", | ||
| "TASK_FAILED": "client_api.task_failed", | ||
| "RESULT_READY": "client_api.result_ready", | ||
| "RESULT_ACCEPTED": "client_api.result_accepted", | ||
| "RESULT_REJECTED": "client_api.result_rejected", | ||
| "LOG": "client_api.log", | ||
| "HEARTBEAT": "client_api.heartbeat", | ||
| "ABORT": "client_api.abort", | ||
| "SHUTDOWN": "client_api.shutdown", | ||
| "BYE": "client_api.bye", | ||
| "ERROR": "client_api.error", | ||
| } | ||
| assert _public_str_values(Topic) == expected | ||
| # every topic is namespaced under the channel prefix | ||
| assert all(value.startswith("client_api.") for value in expected.values()) | ||
|
|
||
| def test_msg_key_wire_values(self): | ||
| # exact frozen wire strings for every message key; a value rename must fail CI | ||
| expected = { | ||
| "SESSION_ID": "session_id", | ||
| "ATTACH_ID": "attach_id", | ||
| "JOB_ID": "job_id", | ||
| "SITE_NAME": "site_name", | ||
| "TRAINER_FQCN": "trainer_fqcn", | ||
| "TARGET_FQCN": "target_fqcn", | ||
| "RANK": "rank", | ||
| "RANK_POLICY": "rank_policy", | ||
| "PROTOCOL_VERSION": "protocol_version", | ||
| "NONCE": "nonce", | ||
| "PROOF": "proof", | ||
| "REASON": "reason", | ||
| "TASK_ID": "task_id", | ||
| "TASK_NAME": "task_name", | ||
| "MODEL": "model", | ||
| "PARAMS": "params", | ||
| "RESULT_ID": "result_id", | ||
| "TRANSFER_ID": "transfer_id", | ||
| "MANIFEST": "manifest", | ||
| } | ||
| assert _public_str_values(MsgKey) == expected | ||
|
|
||
| def test_msg_key_names_present(self): | ||
| # the protocol/Appendix B wire keys must exist by name | ||
| for name in ("TASK_NAME", "MANIFEST", "RANK_POLICY", "MODEL", "PARAMS", "NONCE", "PROOF"): | ||
| assert hasattr(MsgKey, name) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.