fix(ansys): reject invalid named selection jobs

This commit is contained in:
caoqianming 2026-08-19 11:04:46 +08:00
parent e521f832c4
commit fac63f6ab5
6 changed files with 36 additions and 5 deletions

View File

@ -8,6 +8,8 @@
## Unreleased
- 查询专业软件是否可用时不再误提交真实任务ANSYS 静力分析会要求使用几何中已有的 Named Selection并在输入缺少命名选择时返回明确错误。
- Origin 专业软件任务现在会在原 Windows Node 上保留可继续加工的工程;后续调整直接打开上一版工程,过程只提供轻量预览,工程和其他大文件仅在明确要求下载或交付时才传回并登记为正式产物。
- 右侧工作区改为“文件 / 软件作业”双面板,标准页面和 embed 模式保持一致,折叠后仍可查看作业数量与完成提示;文件面板新增“当前对话 / 全部文件”切换、当前目录搜索、排序和修改时间展示。

View File

@ -179,7 +179,8 @@
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[^/\\\\:*?\"<>|]+$"
"pattern": "^[^/\\\\:*?\"<>|]+$",
"description": "Name of an existing Named Selection imported from the input geometry. Never invent or infer this name; ask the user when the geometry does not provide a known name."
},
"vector3": {
"type": "array",

View File

@ -97,6 +97,12 @@ class AnsysContractTests(unittest.TestCase):
with self.assertRaises(SoftwareContractError):
contract.normalize_request(request)
def test_contract_requires_existing_named_selection_names(self) -> None:
contract = get_contract("ansys.mechanical.static_structural@v1")
schema = contract.request_schema["$defs"]["named_selection"]
self.assertIn("existing Named Selection", schema["description"])
self.assertIn("Never invent", schema["description"])
class AnsysWorkerTests(unittest.TestCase):
@classmethod
@ -171,6 +177,15 @@ class AnsysWorkerTests(unittest.TestCase):
with self.assertRaisesRegex(RuntimeError, "NAMED_SELECTION_NOT_FOUND:missing"):
self.worker._named_selections(model, ["missing"])
def test_named_selection_resolution_reports_empty_mechanical_container(self) -> None:
model = mock.Mock()
model.NamedSelections = None
with self.assertRaisesRegex(
RuntimeError,
"NAMED_SELECTION_NOT_FOUND:fixed_face,load_face",
):
self.worker._named_selections(model, ["fixed_face", "load_face"])
def test_acceptance_entry_bypasses_gate_without_enabling_scheduled_execution(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
job_dir = Path(temporary)

View File

@ -114,6 +114,11 @@ class SoftwareJobToolTests(unittest.TestCase):
)
self.assertIn("terminal action for the current run", SoftwareJobSubmitTool.description)
self.assertIn("automatically", SoftwareJobSubmitTool.description)
self.assertIn("never submit a job merely to check", SoftwareJobSubmitTool.description)
self.assertIn(
"checking availability must not submit",
SoftwareCapabilityListTool.description,
)
def test_submit_can_request_automatic_analysis(self):
artifact_id = uuid4()

View File

@ -5,8 +5,8 @@ import json
from uuid import UUID, uuid4
from core.software_contracts import (
get_contracts,
get_contract,
get_contracts,
node_available_slots,
supported_capabilities,
)
@ -44,7 +44,11 @@ class _SoftwareJobTool(Tool):
class SoftwareCapabilityListTool(_SoftwareJobTool):
name = "software_capability_list"
description = "List professional software capabilities available through managed Windows nodes."
description = (
"List professional software capabilities available through managed Windows nodes. "
"Use this tool to answer whether software is available; checking availability must "
"not submit a software job."
)
parameters = {"type": "object", "properties": {}, "additionalProperties": False}
def execute(self) -> str:
@ -68,7 +72,9 @@ class SoftwareJobSubmitTool(_SoftwareJobTool):
name = "software_job_submit"
description = (
"Submit a managed professional-software job using registered artifacts and a "
"capability contract. Call register_artifact first for workspace files. A successful "
"capability contract only when the user requested actual software execution; never "
"submit a job merely to check whether software is available. Call register_artifact "
"first for workspace files. A successful "
"submission is the terminal action for the current run: report the queued job_id once "
"and end the turn. Completion delivery and artifact publication happen automatically "
"in a later system-managed message."

View File

@ -201,7 +201,9 @@ def _write_result_table(path: Path, rows: list[dict[str, Any]]) -> None:
def _named_selections(model: Any, required: list[str]) -> dict[str, Any]:
available: dict[str, Any] = {}
duplicates: set[str] = set()
for item in model.NamedSelections.Children:
container = model.NamedSelections
children = () if container is None else (container.Children or ())
for item in children:
key = str(item.Name).casefold()
if key in available:
duplicates.add(key)