fix(schema): update actor, skill, and tool name validators to accept [[server:]namespace/]name format #9175
@@ -45,6 +45,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
- **Bug Hunt Pool Supervisor Non-Blocking Tracking**: Updated `bug-hunt-pool-supervisor` to make the automation tracking step non-blocking. The `automation-tracking-manager` call in step 5 is now best-effort — if it does not complete within a reasonable time or fails, the supervisor skips it and continues to the next cycle. Added explicit rule 9 clarifying that tracking must never block the main loop. Core functionality (module scanning and worker dispatch) takes priority over status reporting.
|
||||
|
||||
- **Name Validator Server-Qualified Format** (#9074): Updated actor, skill, and tool name
|
||||
validators to accept the spec-required `[[server:]namespace/]name` format. Previously,
|
||||
server-qualified names like `dev:freemo/custom-analysis` were incorrectly rejected.
|
||||
Added BDD scenarios for server-qualified name acceptance and rejection. All three
|
||||
validators (`ActorConfigSchema.validate_name`, `NAMESPACED_NAME_RE`,
|
||||
`_TOOL_NAME_PATTERN`) now correctly support optional server prefixes while maintaining
|
||||
backward compatibility with existing `namespace/name` names.
|
||||
|
||||
- **Automation Profile Silent Fallback** (#8232): `_resolve_profile_for_plan` in
|
||||
`PlanLifecycleService` now raises a clear `ValidationError` when a plan's
|
||||
automation profile name is not a known built-in profile, instead of silently
|
||||
|
||||
@@ -197,6 +197,34 @@ Feature: Actor YAML schema validation
|
||||
Then the actor schema validation should fail
|
||||
And the validation error should contain "namespaced"
|
||||
|
||||
Scenario: Accept actor with server-qualified name
|
||||
Given an actor YAML string with name "dev:freemo/custom-analysis"
|
||||
When I validate the actor schema
|
||||
Then the actor schema validation should succeed
|
||||
|
||||
Scenario: Accept actor with server-qualified name using cleverthis server
|
||||
Given an actor YAML string with name "cleverthis:local/my-actor"
|
||||
When I validate the actor schema
|
||||
Then the actor schema validation should succeed
|
||||
|
||||
Scenario: Reject actor with server-qualified name but multiple slashes
|
||||
Given an actor YAML string with name "dev:namespace/sub/actor"
|
||||
When I validate the actor schema
|
||||
Then the actor schema validation should fail
|
||||
And the validation error should contain "namespaced"
|
||||
|
||||
Scenario: Reject actor with empty server prefix
|
||||
Given an actor YAML string with name ":namespace/name"
|
||||
When I validate the actor schema
|
||||
Then the actor schema validation should fail
|
||||
And the validation error should contain "empty server prefix"
|
||||
|
||||
Scenario: Reject actor with server prefix but no namespace slash
|
||||
Given an actor YAML string with name "dev/server:name"
|
||||
When I validate the actor schema
|
||||
Then the actor schema validation should fail
|
||||
And the validation error should contain "namespace/name after server prefix"
|
||||
|
||||
# ────────────────────────────────────────────────────────────
|
||||
# Invalid LLM Actor scenarios
|
||||
# ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -153,6 +153,16 @@ Feature: Consolidated Tool
|
||||
And the tool model namespace should be "my-ns"
|
||||
And the tool model short name should be "my_tool"
|
||||
|
||||
|
||||
Scenario: Tool name accepts server-qualified format
|
||||
When I create a tool with name "dev:freemo/custom-analysis" and source "builtin"
|
||||
Then the tool model should be created
|
||||
|
||||
|
||||
Scenario: Tool name accepts server-qualified format with cleverthis server
|
||||
When I create a tool with name "cleverthis:local/run-migrations" and source "builtin"
|
||||
Then the tool model should be created
|
||||
|
||||
# ---- Source-conditional field requirements ----
|
||||
|
||||
|
||||
|
||||
@@ -131,6 +131,22 @@ Feature: Skill YAML schema validation
|
||||
Then the skill schema validation should fail
|
||||
And the skill schema error should mention "namespace/name"
|
||||
|
||||
Scenario: Accept skill with server-qualified name
|
||||
Given a skill YAML string with name "dev:freemo/custom-skill"
|
||||
When I validate the skill schema
|
||||
Then the skill schema validation should succeed
|
||||
|
||||
Scenario: Accept skill with server-qualified name using cleverthis server
|
||||
Given a skill YAML string with name "cleverthis:local/my-skill"
|
||||
When I validate the skill schema
|
||||
Then the skill schema validation should succeed
|
||||
|
||||
Scenario: Reject skill with server-qualified name but multiple slashes
|
||||
Given a skill YAML string with name "dev:namespace/sub/skill"
|
||||
When I validate the skill schema expecting failure
|
||||
Then the skill schema validation should fail
|
||||
And the skill schema error should mention "namespace/name"
|
||||
|
||||
Scenario: Invalid namespaced name with special characters
|
||||
Given a skill YAML string with name "local/bad name!!"
|
||||
When I validate the skill schema expecting failure
|
||||
|
||||
@@ -404,6 +404,7 @@ def step_given_actor_with_name(context: Context, name: str) -> None:
|
||||
name: {name}
|
||||
type: llm
|
||||
description: Test actor
|
||||
provider: openai
|
||||
model: gpt-4
|
||||
"""
|
||||
|
||||
|
||||
@@ -810,21 +810,45 @@ class ActorConfigSchema(BaseModel):
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def validate_name(cls, v: str) -> str:
|
||||
"""Ensure actor name follows namespace/name format."""
|
||||
"""Ensure actor name follows [[server:]namespace/]name format.
|
||||
|
||||
Supports:
|
||||
- namespace/name (e.g., 'local/my-actor')
|
||||
- server:namespace/name (e.g., 'dev:freemo/custom-analysis')
|
||||
"""
|
||||
if "/" not in v:
|
||||
msg = f"Actor name must be namespaced (namespace/name): {v}"
|
||||
raise ValueError(msg)
|
||||
|
||||
# Check for exactly one slash
|
||||
parts = v.split("/")
|
||||
if len(parts) != 2:
|
||||
msg = (
|
||||
f"Actor name must be namespaced with exactly one slash "
|
||||
f"(namespace/name): {v}"
|
||||
)
|
||||
raise ValueError(msg)
|
||||
# Split by colon first to extract optional server prefix
|
||||
if ":" in v:
|
||||
server_part, rest = v.split(":", 1)
|
||||
if not server_part:
|
||||
msg = f"Actor name has empty server prefix: {v}"
|
||||
raise ValueError(msg)
|
||||
# rest should be namespace/name
|
||||
if "/" not in rest:
|
||||
msg = f"Actor name must have namespace/name after server prefix: {v}"
|
||||
raise ValueError(msg)
|
||||
parts = rest.split("/")
|
||||
if len(parts) != 2:
|
||||
msg = (
|
||||
f"Actor name must be namespaced with exactly one slash "
|
||||
f"(namespace/name after optional server prefix): {v}"
|
||||
)
|
||||
raise ValueError(msg)
|
||||
namespace, name = parts
|
||||
else:
|
||||
# No server prefix, just namespace/name
|
||||
parts = v.split("/")
|
||||
if len(parts) != 2:
|
||||
msg = (
|
||||
f"Actor name must be namespaced with exactly one slash "
|
||||
f"(namespace/name): {v}"
|
||||
)
|
||||
raise ValueError(msg)
|
||||
namespace, name = parts
|
||||
|
||||
namespace, name = parts
|
||||
if not namespace or not name:
|
||||
msg = (
|
||||
f"Actor name must be namespaced with non-empty namespace and name: {v}"
|
||||
|
||||
@@ -77,7 +77,7 @@ from cleveragents.domain.models.core.execution_environment_preference import (
|
||||
# Regex patterns
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_TOOL_NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$")
|
||||
_TOOL_NAME_PATTERN = re.compile(r"^(?:[a-zA-Z0-9_-]+:)?[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
|
||||
@@ -38,7 +38,10 @@ logger = logging.getLogger(__name__)
|
||||
# ────────────────────────────────────────────────────────────
|
||||
|
||||
#: Pattern for ``<namespace>/<name>`` with hyphens, underscores, lowercase alphanum.
|
||||
NAMESPACED_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*/[a-z0-9][a-z0-9_-]*$")
|
||||
#: Supports optional server prefix: ``[server:]namespace/name``
|
||||
NAMESPACED_NAME_RE = re.compile(
|
||||
r"^(?:[a-z0-9][a-z0-9_-]*:)?[a-z0-9][a-z0-9_-]*/[a-z0-9][a-z0-9_-]*$"
|
||||
)
|
||||
|
||||
#: Pattern for ``${VAR}`` environment variable references.
|
||||
_ENV_VAR_RE = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
||||
|
||||
Reference in New Issue
Block a user