Skip to content

Commit ea75548

Browse files
seedspiritclaude
andcommitted
fix(BA-6015): reject empty model definition_path and treat blanks as missing in merge
Pydantic now rejects empty strings at the DTO boundary via min_length=1, and _merge_mounts uses a truthy check so any empty string that bypasses the DTO no longer clobbers a resolved lower-priority model_definition_path. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 76b0091 commit ea75548

4 files changed

Lines changed: 33 additions & 5 deletions

File tree

src/ai/backend/common/dto/manager/v2/deployment/request.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,12 @@ class ModelMountConfigInput(BaseRequestModel):
266266
mount_destination: str = Field(description="Mount destination path inside container")
267267
definition_path: str | None = Field(
268268
default=None,
269+
min_length=1,
269270
description=(
270271
"Optional path to the model definition file within the model vfolder. "
271272
"When omitted, the server auto-detects `model-definition.yaml` or "
272-
"`model-definition.yml`."
273+
"`model-definition.yml`. Empty string is rejected; omit the field to "
274+
"trigger auto-detection."
273275
),
274276
)
275277
subpath: str | None = Field(

src/ai/backend/manager/data/deployment/types.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -668,13 +668,11 @@ def _merge_mounts(
668668
return MountMetadata(
669669
model_vfolder_id=upper.model_vfolder_id,
670670
model_definition_path=upper.model_definition_path
671-
if upper.model_definition_path is not None
671+
if upper.model_definition_path
672672
else lower.model_definition_path,
673673
model_mount_destination=upper.model_mount_destination,
674674
extra_mounts=list(upper.extra_mounts),
675-
vfolder_subpath=upper.vfolder_subpath
676-
if upper.vfolder_subpath is not None
677-
else lower.vfolder_subpath,
675+
vfolder_subpath=upper.vfolder_subpath if upper.vfolder_subpath else lower.vfolder_subpath,
678676
)
679677

680678

tests/unit/common/dto/manager/v2/deployment/test_request.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ def _make_create_revision_input_dto(**kwargs: object) -> CreateRevisionInput:
7070
return CreateRevisionInput(**defaults)
7171

7272

73+
class TestModelMountConfigInput:
74+
"""Tests for ModelMountConfigInput model."""
75+
76+
def test_definition_path_defaults_to_none(self) -> None:
77+
config = ModelMountConfigInput(
78+
vfolder_id=VFolderUUID(uuid.uuid4()),
79+
mount_destination="/models",
80+
)
81+
assert config.definition_path is None
82+
83+
def test_empty_definition_path_raises_validation_error(self) -> None:
84+
with pytest.raises((BackendAISchemaValidationFailed, ValidationError)):
85+
ModelMountConfigInput.model_validate({
86+
"vfolder_id": str(uuid.uuid4()),
87+
"mount_destination": "/models",
88+
"definition_path": "",
89+
})
90+
91+
7392
class TestExtraVFolderMountInput:
7493
"""Tests for ExtraVFolderMountInput model."""
7594

tests/unit/manager/data/deployment/test_revision_draft_merge.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,12 @@ def test_mount_definition_path_preserves_lower_value_when_higher_omits_it(self)
160160

161161
assert merged.mounts is not None
162162
assert merged.mounts.model_definition_path == "model-definition.yml"
163+
164+
def test_mount_definition_path_treats_empty_string_as_missing(self) -> None:
165+
base = RevisionDraft(mounts=_mounts("model-definition.yml"))
166+
override = RevisionDraft(mounts=_mounts(""))
167+
168+
merged = base.merge(override)
169+
170+
assert merged.mounts is not None
171+
assert merged.mounts.model_definition_path == "model-definition.yml"

0 commit comments

Comments
 (0)