From 0fe08570fc416b538fd8750bb0856cfe38e351dd Mon Sep 17 00:00:00 2001 From: mschwab Date: Wed, 29 Jul 2026 14:22:42 -0700 Subject: [PATCH 1/7] fix(api): declare the entity-store name pattern on create DTOs [ASTD-349] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spec advertised `^[\w\-.]+$` (max 255) for fileset, model provider, and secret names while the entity store enforces the stricter RFC-1035-ish NAME_PATTERN downstream. Names like `Sparl` or `My_Provider` passed every generated client's validation and then failed with a 422, so the published contract was wrong for all SDK consumers, not just Studio. Declare NAME_PATTERN on the three create-request DTOs. Each needs regex_engine="python-re" — the pattern uses lookaround, which Pydantic's default Rust engine rejects — matching what the entity-store schemas already do. max_length drops 255 -> 63 to agree with the regex, which caps at 63 by construction. The secrets DTO enforced its rule in a field_validator, so no pattern reached the spec at all; moving it to `pattern=` publishes it. Its uppercase-name regression tests now bypass the request model with model_construct so they still exercise the server-side 422 path. `@` is legal under NAME_PATTERN, so secrets now accept `a@b`. The entity store always did. Signed-off-by: mschwab --- openapi/ga/individual/platform.openapi.yaml | 25 +++++++----- openapi/ga/openapi.yaml | 25 +++++++----- openapi/openapi.yaml | 25 +++++++----- .../src/nemo_platform_plugin/files/types.py | 21 ++++++---- .../src/nemo_platform_plugin/secrets/types.py | 38 +++++++++---------- .../src/nmp/common/entities/constants.py | 2 + .../models/src/nmp/core/models/schemas.py | 10 +++-- .../integration/test_secrets_with_auth.py | 20 ++++++++-- services/core/secrets/tests/test_secrets.py | 22 +++++++---- 9 files changed, 116 insertions(+), 72 deletions(-) diff --git a/openapi/ga/individual/platform.openapi.yaml b/openapi/ga/individual/platform.openapi.yaml index 234a7c54aea..38190d9fefd 100644 --- a/openapi/ga/individual/platform.openapi.yaml +++ b/openapi/ga/individual/platform.openapi.yaml @@ -9416,11 +9416,12 @@ components: properties: name: type: string - maxLength: 255 - pattern: ^[\w\-.]+$ + maxLength: 63 + pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? str: - if not _NAME_RE.match(v): - raise ValueError( - f"Invalid secret name '{v}'. Allowed characters: letters, digits, underscores, " - "hyphens, and dots. Example: my-api-key" - ) - return v - @field_serializer("value", when_used="json") def _serialize_value(self, value: SecretStr) -> str: return value.get_secret_value() diff --git a/packages/nmp_common/src/nmp/common/entities/constants.py b/packages/nmp_common/src/nmp/common/entities/constants.py index 5977d6baa62..937c86ffe81 100644 --- a/packages/nmp_common/src/nmp/common/entities/constants.py +++ b/packages/nmp_common/src/nmp/common/entities/constants.py @@ -12,6 +12,8 @@ # TODO(#3530): Remove @, ., +, _ once versioning is implemented and predefined target names (e.g., llama-3.2-3b-instruct@v1.0.0+A100) are updated. NAME_PATTERN = r"^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? str: @@ -473,10 +473,12 @@ class ModelProviderSort(StrEnum): class CreateModelProviderRequest(BaseModel): """Request model for creating a ModelProvider.""" + model_config = ConfigDict(regex_engine="python-re") + name: str = Field( - description=f"Name of the model provider. {constants.REGEX_WORD_CHARACTER_DOT_DASH_DESCRIPTION}", - max_length=constants.MAX_LENGTH_255, - pattern=constants.REGEX_WORD_CHARACTER_DOT_DASH, + description=f"Name of the model provider. {constants.NAME_PATTERN_DESCRIPTION}", + max_length=constants.NAME_MAX_LENGTH, + pattern=constants.NAME_PATTERN, examples=["my-nim-provider", "openai-endpoint"], ) project: Optional[str] = Field( diff --git a/services/core/secrets/tests/integration/test_secrets_with_auth.py b/services/core/secrets/tests/integration/test_secrets_with_auth.py index f1caac08d6b..58c04fc9088 100644 --- a/services/core/secrets/tests/integration/test_secrets_with_auth.py +++ b/services/core/secrets/tests/integration/test_secrets_with_auth.py @@ -34,7 +34,7 @@ short_unique_name, unique_email, ) -from pydantic import SecretStr +from pydantic import SecretStr, ValidationError # Service principals have elevated access (like platform admin) SERVICE_PRINCIPAL = "service:integration-test" @@ -997,9 +997,17 @@ def test_create_secret_with_uppercase_returns_422(self, sdk: NeMoPlatform): # Name with uppercase letter - violates DNS-compliant naming rules invalid_name = "test-secret-123-Test" + with pytest.raises(ValidationError) as local_exc: + PlatformSecretCreateRequest(name=invalid_name, value=SecretStr("test-value")) + assert "should match pattern" in str(local_exc.value).lower() + + # model_construct skips validation so the bad name still reaches the server, + # which is where the 500-vs-422 regression lived. with pytest.raises(ClientUnprocessableEntityError) as exc_info: admin_secrets.create_secret( - body=PlatformSecretCreateRequest(name=invalid_name, value=SecretStr("test-value")), + body=PlatformSecretCreateRequest.model_construct( + name=invalid_name, value=SecretStr("test-value"), description=None + ), workspace="default", ) @@ -1016,9 +1024,15 @@ def test_create_secret_with_all_uppercase_returns_422(self, sdk: NeMoPlatform): admin_secrets = client_from_platform(admin_sdk, SecretsClient) invalid_name = "TEST-SECRET" + with pytest.raises(ValidationError) as local_exc: + PlatformSecretCreateRequest(name=invalid_name, value=SecretStr("test-value")) + assert "should match pattern" in str(local_exc.value).lower() + with pytest.raises(ClientUnprocessableEntityError) as exc_info: admin_secrets.create_secret( - body=PlatformSecretCreateRequest(name=invalid_name, value=SecretStr("test-value")), + body=PlatformSecretCreateRequest.model_construct( + name=invalid_name, value=SecretStr("test-value"), description=None + ), workspace="default", ) diff --git a/services/core/secrets/tests/test_secrets.py b/services/core/secrets/tests/test_secrets.py index ef3aed559d6..6b33b264ca9 100644 --- a/services/core/secrets/tests/test_secrets.py +++ b/services/core/secrets/tests/test_secrets.py @@ -65,18 +65,26 @@ async def test_create_secret_with_empty_value(test_client): assert response.status_code == 422 -@pytest.mark.parametrize("invalid_name", ["bad name", "bad/name", "no!way", "a@b"]) -async def test_create_secret_with_invalid_name_returns_friendly_error(test_client, invalid_name): - """Verify that names with disallowed characters return a 422 with a readable message.""" +@pytest.mark.parametrize( + "invalid_name", + ["bad name", "bad/name", "no!way", "MySecret", "1secret", "my--secret", "secret-", "a" * 64], +) +async def test_create_secret_with_invalid_name_is_rejected(test_client, invalid_name): + """Names the entity store would reject must fail at the API boundary, not downstream.""" response = test_client.post( "/apis/secrets/v2/workspaces/default/secrets", json={"name": invalid_name, "value": "x"}, ) assert response.status_code == 422 - detail = response.json()["detail"] - msg = detail[0]["msg"] - assert "Invalid secret name" in msg - assert invalid_name in msg + + +@pytest.mark.parametrize("valid_name", ["hf-token", "a@b", "model.v1", "svc_key", "ab"]) +async def test_create_secret_accepts_entity_store_names(test_client, valid_name): + response = test_client.post( + "/apis/secrets/v2/workspaces/default/secrets", + json={"name": valid_name, "value": "x"}, + ) + assert response.status_code == 201 async def test_create_and_delete_secret(test_client): From ac0f2dd36c0cc69c551ea6721be04396d5abcd73 Mon Sep 17 00:00:00 2001 From: mschwab Date: Wed, 29 Jul 2026 14:41:54 -0700 Subject: [PATCH 2/7] refactor(api): collapse the mirrored entity name constants [ASTD-349] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NAME_PATTERN was copy-pasted verbatim in four Python modules — nmp_common, plus files/types.py, secrets/types.py, and jobs/spec.py inside nemo_platform_plugin. Each carried a comment explaining it was inlined to avoid an nmp_common dependency, and jobs/spec.py cited files/types.py as the precedent, so the duplication was self-propagating. The dependency only runs one way: nmp_common depends on nemo-platform-plugin, so the plugin cannot import nmp_common without a cycle. That makes the plugin the correct home. New leaf module entity_naming.py holds the single definition and imports nothing, so modules that need to stay leaf nodes still can. nmp_common re-exports it, leaving every existing constants.NAME_PATTERN call site untouched. Pure refactor: regenerating the OpenAPI spec produces no diff. Signed-off-by: mschwab --- .../src/nemo_platform_plugin/entity_naming.py | 33 +++++++++++++++++++ .../src/nemo_platform_plugin/files/types.py | 11 +------ .../src/nemo_platform_plugin/jobs/spec.py | 12 +------ .../src/nemo_platform_plugin/secrets/types.py | 19 +++-------- .../src/nmp/common/entities/constants.py | 25 ++++++-------- 5 files changed, 49 insertions(+), 51 deletions(-) create mode 100644 packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py diff --git a/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py b/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py new file mode 100644 index 00000000000..65bcf8883f6 --- /dev/null +++ b/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""The single definition of the entity name rule. + +This lives in ``nemo_platform_plugin`` rather than ``nmp_common`` because the +dependency runs that way — ``nmp_common`` depends on this package, so anything +here is importable from both sides. ``nmp.common.entities.constants`` re-exports +these names. + +Imports nothing, so modules that need to stay leaf nodes can still use it. + +Any Pydantic model declaring ``pattern=NAME_PATTERN`` must also set +``model_config = ConfigDict(regex_engine="python-re")``; the pattern uses +lookaround, which Pydantic's default Rust engine rejects. +""" + +# RFC 1035 compliant pattern with temporary support for special characters. +# Allows lowercase letters, digits, hyphens, and temporarily: @, ., +, _ +# - Must start with a lowercase letter [a-z] +# - Length: 2-63 characters +# - No consecutive hyphens (--) +# - Must not end with a hyphen +# TODO(#3530): Remove @, ., +, _ once versioning is implemented and predefined target names (e.g., llama-3.2-3b-instruct@v1.0.0+A100) are updated. +NAME_PATTERN = r"^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? Date: Wed, 29 Jul 2026 15:03:15 -0700 Subject: [PATCH 3/7] refactor(api): trim comments on entity name validation [ASTD-349] Signed-off-by: mschwab --- .../src/nemo_platform_plugin/entity_naming.py | 14 +++----------- .../src/nmp/common/entities/constants.py | 2 -- .../tests/integration/test_secrets_with_auth.py | 3 +-- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py b/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py index 65bcf8883f6..a3cbcd41c10 100644 --- a/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py +++ b/packages/nemo_platform_plugin/src/nemo_platform_plugin/entity_naming.py @@ -1,18 +1,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""The single definition of the entity name rule. +"""Single definition of the entity name rule; ``nmp.common`` re-exports it. -This lives in ``nemo_platform_plugin`` rather than ``nmp_common`` because the -dependency runs that way — ``nmp_common`` depends on this package, so anything -here is importable from both sides. ``nmp.common.entities.constants`` re-exports -these names. - -Imports nothing, so modules that need to stay leaf nodes can still use it. - -Any Pydantic model declaring ``pattern=NAME_PATTERN`` must also set -``model_config = ConfigDict(regex_engine="python-re")``; the pattern uses -lookaround, which Pydantic's default Rust engine rejects. +Lives here because ``nmp_common`` depends on this package, not the reverse. +Models using ``pattern=NAME_PATTERN`` need ``regex_engine="python-re"``. """ # RFC 1035 compliant pattern with temporary support for special characters. diff --git a/packages/nmp_common/src/nmp/common/entities/constants.py b/packages/nmp_common/src/nmp/common/entities/constants.py index eae1113835d..cfa93945fe3 100644 --- a/packages/nmp_common/src/nmp/common/entities/constants.py +++ b/packages/nmp_common/src/nmp/common/entities/constants.py @@ -3,8 +3,6 @@ """Constants for entity validation.""" -# Defined in nemo_platform_plugin so plugins can reach it without depending on -# nmp_common, which would be circular. Re-exported here for existing callers. from nemo_platform_plugin.entity_naming import ( NAME_MAX_LENGTH as NAME_MAX_LENGTH, ) diff --git a/services/core/secrets/tests/integration/test_secrets_with_auth.py b/services/core/secrets/tests/integration/test_secrets_with_auth.py index 58c04fc9088..e79a8ef3532 100644 --- a/services/core/secrets/tests/integration/test_secrets_with_auth.py +++ b/services/core/secrets/tests/integration/test_secrets_with_auth.py @@ -1001,8 +1001,7 @@ def test_create_secret_with_uppercase_returns_422(self, sdk: NeMoPlatform): PlatformSecretCreateRequest(name=invalid_name, value=SecretStr("test-value")) assert "should match pattern" in str(local_exc.value).lower() - # model_construct skips validation so the bad name still reaches the server, - # which is where the 500-vs-422 regression lived. + # model_construct skips validation so the name still reaches the server. with pytest.raises(ClientUnprocessableEntityError) as exc_info: admin_secrets.create_secret( body=PlatformSecretCreateRequest.model_construct( From 8809843e785fdc9ae1744257c127674d4a66a703 Mon Sep 17 00:00:00 2001 From: mschwab Date: Wed, 29 Jul 2026 15:23:36 -0700 Subject: [PATCH 4/7] fix(api): document the special characters NAME_PATTERN accepts [ASTD-349] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NAME_PATTERN_DESCRIPTION claimed names may contain "only lowercase letters, digits, and hyphens", but the pattern has allowed @, ., + and _ since it was written — see the TODO(#3530) directly above it. The spec therefore told SDK consumers a name like llama-3.2-3b@v1.0.0 was invalid when the service accepts it, and contradicted Studio's own help text, which lists the special characters correctly. All nine name fields in the spec now carry wording that matches the nine patterns beside them. Reported by CodeRabbit on #978. Signed-off-by: mschwab --- openapi/ga/individual/platform.openapi.yaml | 28 ++++++++++--------- openapi/ga/openapi.yaml | 28 ++++++++++--------- openapi/openapi.yaml | 28 ++++++++++--------- .../src/nemo_platform_plugin/entity_naming.py | 2 +- 4 files changed, 46 insertions(+), 40 deletions(-) diff --git a/openapi/ga/individual/platform.openapi.yaml b/openapi/ga/individual/platform.openapi.yaml index 38190d9fefd..3cc37b00d5a 100644 --- a/openapi/ga/individual/platform.openapi.yaml +++ b/openapi/ga/individual/platform.openapi.yaml @@ -9420,8 +9420,8 @@ components: pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? Date: Thu, 30 Jul 2026 00:07:11 -0700 Subject: [PATCH 5/7] chore(sdk): regenerate SDK and CLI for the name description [ASTD-349] Picks up the NAME_PATTERN_DESCRIPTION change across the vendored Python SDK, the generated CLI commands, and the CLI reference docs. plugins/nemo-customizer/openapi/openapi.yaml is deliberately excluded: regenerating it locally drops the whole rl/jobs surface, which comes from an optional dependency that isn't installed here. Signed-off-by: mschwab --- docs/cli/reference.mdx | 6 +-- .../cli/commands/api/files/filesets.py | 4 +- .../cli/commands/api/inference/providers.py | 4 +- .../cli/commands/api/projects.py | 4 +- .../cli/commands/api/workspaces/__init__.py | 4 +- .../nemo-platform/.nmpcontext/openapi.yaml | 45 +++++++++++-------- .../cli/commands/api/files/filesets.py | 4 +- .../cli/commands/api/inference/providers.py | 4 +- .../cli/commands/api/projects.py | 4 +- .../cli/commands/api/workspaces/__init__.py | 4 +- .../resources/entities/entities.py | 14 +++--- .../nemo_platform/resources/files/filesets.py | 10 +++-- .../resources/inference/providers.py | 10 +++-- .../resources/inference/virtual_models.py | 6 +-- .../resources/projects/projects.py | 8 ++-- .../resources/secrets/secrets.py | 10 +++-- .../resources/workspaces/workspaces.py | 8 ++-- .../types/entities/entity_create_params.py | 4 +- .../entity_update_entity_by_name_params.py | 4 +- .../types/files/fileset_create_params.py | 5 ++- .../types/inference/provider_create_params.py | 5 ++- .../types/jobs/platform_job_step_spec.py | 4 +- .../jobs/platform_job_step_spec_param.py | 4 +- .../types/projects/project_create_params.py | 4 +- .../types/secrets/secret_create_params.py | 5 ++- .../workspaces/workspace_create_params.py | 4 +- 26 files changed, 103 insertions(+), 85 deletions(-) diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx index 52ace1f5cbb..8302ed1c5bc 100644 --- a/docs/cli/reference.mdx +++ b/docs/cli/reference.mdx @@ -1100,7 +1100,7 @@ nemo files filesets create [OPTIONS] [NAME] **Arguments:** -* ``: The name of the fileset. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. +* ``: The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). **Options:** @@ -3144,7 +3144,7 @@ nemo inference providers create [OPTIONS] [NAME] **Arguments:** -* ``: Name of the model provider. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. +* ``: Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). **Options:** @@ -5066,7 +5066,7 @@ nemo workspaces create [OPTIONS] [NAME] **Arguments:** -* ``: Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, and hyphens (no consecutive hyphens, cannot end with a hyphen). +* ``: Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). **Options:** diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py index 9ea72faeeb9..14bd05b5353 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py @@ -34,7 +34,7 @@ def create_filesets( name: Annotated[ str | None, typer.Argument( - help="The name of the fileset. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. (required)" + help="The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -127,7 +127,7 @@ def create_filesets( ["name"], "files filesets create", { - "name": "The name of the fileset. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. (required)", + "name": "The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py index 7cd01b59374..5a9cee20499 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py @@ -34,7 +34,7 @@ def create_providers( name: Annotated[ str | None, typer.Argument( - help="Name of the model provider. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. (required)" + help="Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -179,7 +179,7 @@ def create_providers( "inference providers create", { "host_url": "The network endpoint URL for the model provider (required)", - "name": "Name of the model provider. Allowed characters: letters (a-z, A-Z), digits (0-9), underscores, hyphens, and dots. (required)", + "name": "Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py index d7e2623feb0..482c1d18920 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py @@ -34,7 +34,7 @@ def create_projects( name: Annotated[ str | None, typer.Argument( - help="Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, and hyphens (no consecutive hyphens, cannot end with a hyphen). (required)" + help="Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -95,7 +95,7 @@ def create_projects( ["name"], "projects create", { - "name": "Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, and hyphens (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py index e0a5be77913..d83f7e03357 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py @@ -41,7 +41,7 @@ def create_workspaces( name: Annotated[ str | None, typer.Argument( - help="Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, and hyphens (no consecutive hyphens, cannot end with a hyphen). (required)" + help="Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, wait_role_propagation: Annotated[ @@ -112,7 +112,7 @@ def create_workspaces( ["name"], "workspaces create", { - "name": "Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, and hyphens (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml index 234a7c54aea..3cc37b00d5a 100644 --- a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml +++ b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml @@ -9416,11 +9416,12 @@ components: properties: name: type: string - maxLength: 255 - pattern: ^[\w\-.]+$ + maxLength: 63 + pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? Date: Thu, 30 Jul 2026 01:23:21 -0700 Subject: [PATCH 6/7] fix(api): reword the entity name description [ASTD-349] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems with the previous wording, "contain only lowercase letters, digits, hyphens, and @ . + _". The trailing underscore is markdown-escaped downstream, so the CLI reference rendered "@ . + \__". Dropping the literal characters removes the artifact. Second, @, + and _ are slated for removal (TODO(#3530)), and advertising them invites adoption ahead of that migration — mckornfield's point on this PR. Dots are not in the same category: llama-3.1-8b is the example in CreateModelProviderRequest and dotted versions are how model names are written, so those stay documented. "use" rather than "contain only" keeps this from being a false claim about what the service rejects; the machine-readable pattern remains the contract. Signed-off-by: mschwab --- openapi/ga/individual/platform.openapi.yaml | 44 +++++++++---------- openapi/ga/openapi.yaml | 44 +++++++++---------- openapi/openapi.yaml | 44 +++++++++---------- .../src/nemo_platform_plugin/entity_naming.py | 2 +- 4 files changed, 64 insertions(+), 70 deletions(-) diff --git a/openapi/ga/individual/platform.openapi.yaml b/openapi/ga/individual/platform.openapi.yaml index 3cc37b00d5a..359cbae96a2 100644 --- a/openapi/ga/individual/platform.openapi.yaml +++ b/openapi/ga/individual/platform.openapi.yaml @@ -9420,8 +9420,8 @@ components: pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(? Date: Thu, 30 Jul 2026 07:05:52 -0700 Subject: [PATCH 7/7] chore(sdk): regenerate SDK and CLI for the reworded description [ASTD-349] Propagates the NAME_PATTERN_DESCRIPTION rewording through the vendored Python SDK, the generated CLI commands, and the CLI reference docs. Also clears the "@ . + \__" escaping artifact the previous wording left in docs/cli/reference.mdx. Signed-off-by: mschwab --- docs/cli/reference.mdx | 6 +-- .../cli/commands/api/files/filesets.py | 4 +- .../cli/commands/api/inference/providers.py | 4 +- .../cli/commands/api/projects.py | 4 +- .../cli/commands/api/workspaces/__init__.py | 4 +- .../nemo-platform/.nmpcontext/openapi.yaml | 44 +++++++++---------- .../cli/commands/api/files/filesets.py | 4 +- .../cli/commands/api/inference/providers.py | 4 +- .../cli/commands/api/projects.py | 4 +- .../cli/commands/api/workspaces/__init__.py | 4 +- .../resources/entities/entities.py | 18 ++++---- .../nemo_platform/resources/files/filesets.py | 8 ++-- .../resources/inference/providers.py | 8 ++-- .../resources/projects/projects.py | 8 ++-- .../resources/secrets/secrets.py | 8 ++-- .../resources/workspaces/workspaces.py | 8 ++-- .../types/entities/entity_create_params.py | 6 +-- .../entity_update_entity_by_name_params.py | 6 +-- .../types/files/fileset_create_params.py | 6 +-- .../types/inference/provider_create_params.py | 6 +-- .../types/jobs/platform_job_step_spec.py | 4 +- .../jobs/platform_job_step_spec_param.py | 4 +- .../types/projects/project_create_params.py | 6 +-- .../types/secrets/secret_create_params.py | 6 +-- .../workspaces/workspace_create_params.py | 6 +-- 25 files changed, 93 insertions(+), 97 deletions(-) diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx index 8302ed1c5bc..ac3acaa3fa1 100644 --- a/docs/cli/reference.mdx +++ b/docs/cli/reference.mdx @@ -1100,7 +1100,7 @@ nemo files filesets create [OPTIONS] [NAME] **Arguments:** -* ``: The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). +* ``: The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). **Options:** @@ -3144,7 +3144,7 @@ nemo inference providers create [OPTIONS] [NAME] **Arguments:** -* ``: Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). +* ``: Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). **Options:** @@ -5066,7 +5066,7 @@ nemo workspaces create [OPTIONS] [NAME] **Arguments:** -* ``: Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). +* ``: Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). **Options:** diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py index 14bd05b5353..b11d28b5aa5 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/files/filesets.py @@ -34,7 +34,7 @@ def create_filesets( name: Annotated[ str | None, typer.Argument( - help="The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" + help="The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -127,7 +127,7 @@ def create_filesets( ["name"], "files filesets create", { - "name": "The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "The name of the fileset. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py index 5a9cee20499..44c6e126df2 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/inference/providers.py @@ -34,7 +34,7 @@ def create_providers( name: Annotated[ str | None, typer.Argument( - help="Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" + help="Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -179,7 +179,7 @@ def create_providers( "inference providers create", { "host_url": "The network endpoint URL for the model provider (required)", - "name": "Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "Name of the model provider. Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py index 482c1d18920..073d10a3588 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/projects.py @@ -34,7 +34,7 @@ def create_projects( name: Annotated[ str | None, typer.Argument( - help="Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" + help="Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, workspace: Annotated[str | None, typer.Option("--workspace")] = None, @@ -95,7 +95,7 @@ def create_projects( ["name"], "projects create", { - "name": "Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "Project name (unique within workspace). Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py index d83f7e03357..30d201254da 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/workspaces/__init__.py @@ -41,7 +41,7 @@ def create_workspaces( name: Annotated[ str | None, typer.Argument( - help="Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \\__ (no consecutive hyphens, cannot end with a hyphen). (required)" + help="Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)" ), ] = None, wait_role_propagation: Annotated[ @@ -112,7 +112,7 @@ def create_workspaces( ["name"], "workspaces create", { - "name": "Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and contain only lowercase letters, digits, hyphens, and @ . + \__ (no consecutive hyphens, cannot end with a hyphen). (required)", + "name": "Workspace name (unique identifier). Name must start with a lowercase letter, be 2-63 characters, and use lowercase letters, digits, hyphens, and dots (no consecutive hyphens, cannot end with a hyphen). (required)", }, ) diff --git a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml index 3cc37b00d5a..359cbae96a2 100644 --- a/sdk/python/nemo-platform/.nmpcontext/openapi.yaml +++ b/sdk/python/nemo-platform/.nmpcontext/openapi.yaml @@ -9420,8 +9420,8 @@ components: pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(?