UAT: NamespacedName.parse() in plan.py silently accepts leading-slash names (e.g., "/my-action") and maps them to local/my-action instead of raising a validation error #2160

Open
opened 2026-04-03 04:33:41 +00:00 by freemo · 0 comments
Owner

Metadata

  • Branch: bugfix/namespacedname-parse-leading-slash-validation
  • Commit Message: fix(plan): reject leading-slash names in NamespacedName.parse() with a ValueError
  • Milestone: v3.7.0
  • Parent Epic: #362

Background and Context

Code-level analysis of src/cleveragents/domain/models/core/plan.py revealed that NamespacedName.parse() silently accepts malformed input with a leading slash (e.g., "/my-action") and maps it to local/my-action instead of raising a ValueError.

Per docs/specification.md §Namespaces and ADR-002, the valid name format is [[server:]namespace/]name. A leading slash (/my-action) is not a valid form — the component before the slash would be an empty string, which is not a legal namespace identifier.

The parse_namespaced_name() function in project.py correctly rejects this input using a regex-based approach. NamespacedName.parse() in plan.py does not, creating an inconsistency between the two parsing paths.

Steps to Reproduce

from cleveragents.domain.models.core.plan import NamespacedName

# Should raise ValueError but silently succeeds:
nn = NamespacedName.parse("/my-action")
print(nn)  # Prints: local/my-action  ← WRONG

# Compare with the correct behaviour in project.py:
from cleveragents.domain.models.core.project import parse_namespaced_name
parse_namespaced_name("/my-action")  # Raises ValueError (correct!)

Root Cause

In NamespacedName.parse() (plan.py ~line 239):

if "/" in name:                          # True — has slash
    namespace, name = name.split("/", 1)
    # namespace = ""  (empty string before the slash)
    # name = "my-action"

return cls(server=None, namespace="", name="my-action")
# validate_namespace("") returns "local" (because `if not v: return "local"`)
# Result: NamespacedName(server=None, namespace="local", name="my-action")

The validate_namespace() method's if not v: return "local" branch silently coerces an empty namespace (produced by a leading slash) into "local", masking the malformed input.

Expected Behaviour

NamespacedName.parse("/my-action") must raise a ValueError with a clear message indicating that a leading slash is not a valid name format, consistent with parse_namespaced_name() in project.py.

Affected Files

  • src/cleveragents/domain/models/core/plan.pyNamespacedName.parse() (~line 239) and NamespacedName.validate_namespace() (~line 221, the if not v: return "local" branch)
  • Reference (correct): src/cleveragents/domain/models/core/project.pyparse_namespaced_name() which uses regex and rejects this input

Subtasks

  • Add a guard at the top of NamespacedName.parse() that raises ValueError if the input string starts with "/" (before any splitting logic)
  • Alternatively (or additionally), remove the if not v: return "local" silent-coercion branch from validate_namespace() and replace it with an explicit ValueError for empty namespace strings
  • Write a Behave unit test scenario (in features/) that asserts NamespacedName.parse("/my-action") raises ValueError
  • Write a Behave unit test scenario confirming NamespacedName.parse("my-action") (no slash) still returns local/my-action correctly
  • Audit all call sites of NamespacedName.parse() in the codebase to confirm none pass leading-slash strings intentionally
  • Run nox -e typecheck and confirm no Pyright errors are introduced
  • Run nox -e unit_tests and confirm all scenarios pass
  • Run nox (all default sessions) and fix any errors
  • Verify coverage >= 97% via nox -e coverage_report

Definition of Done

  • NamespacedName.parse("/my-action") raises ValueError with a descriptive message
  • NamespacedName.parse("/my-action") behaviour is consistent with parse_namespaced_name("/my-action") in project.py
  • NamespacedName.parse("my-action") (bare name, no slash) continues to return local/my-action correctly
  • A Behave feature scenario covers the leading-slash rejection case
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done
  • All nox stages pass
  • Coverage >= 97%

Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Metadata - **Branch**: `bugfix/namespacedname-parse-leading-slash-validation` - **Commit Message**: `fix(plan): reject leading-slash names in NamespacedName.parse() with a ValueError` - **Milestone**: v3.7.0 - **Parent Epic**: #362 ## Background and Context Code-level analysis of `src/cleveragents/domain/models/core/plan.py` revealed that `NamespacedName.parse()` silently accepts malformed input with a leading slash (e.g., `"/my-action"`) and maps it to `local/my-action` instead of raising a `ValueError`. Per `docs/specification.md` §Namespaces and ADR-002, the valid name format is `[[server:]namespace/]name`. A leading slash (`/my-action`) is not a valid form — the component before the slash would be an empty string, which is not a legal namespace identifier. The `parse_namespaced_name()` function in `project.py` correctly rejects this input using a regex-based approach. `NamespacedName.parse()` in `plan.py` does not, creating an inconsistency between the two parsing paths. ## Steps to Reproduce ```python from cleveragents.domain.models.core.plan import NamespacedName # Should raise ValueError but silently succeeds: nn = NamespacedName.parse("/my-action") print(nn) # Prints: local/my-action ← WRONG # Compare with the correct behaviour in project.py: from cleveragents.domain.models.core.project import parse_namespaced_name parse_namespaced_name("/my-action") # Raises ValueError (correct!) ``` ## Root Cause In `NamespacedName.parse()` (plan.py ~line 239): ```python if "/" in name: # True — has slash namespace, name = name.split("/", 1) # namespace = "" (empty string before the slash) # name = "my-action" return cls(server=None, namespace="", name="my-action") # validate_namespace("") returns "local" (because `if not v: return "local"`) # Result: NamespacedName(server=None, namespace="local", name="my-action") ``` The `validate_namespace()` method's `if not v: return "local"` branch silently coerces an empty namespace (produced by a leading slash) into `"local"`, masking the malformed input. ## Expected Behaviour `NamespacedName.parse("/my-action")` must raise a `ValueError` with a clear message indicating that a leading slash is not a valid name format, consistent with `parse_namespaced_name()` in `project.py`. ## Affected Files - `src/cleveragents/domain/models/core/plan.py` — `NamespacedName.parse()` (~line 239) and `NamespacedName.validate_namespace()` (~line 221, the `if not v: return "local"` branch) - Reference (correct): `src/cleveragents/domain/models/core/project.py` — `parse_namespaced_name()` which uses regex and rejects this input ## Subtasks - [ ] Add a guard at the top of `NamespacedName.parse()` that raises `ValueError` if the input string starts with `"/"` (before any splitting logic) - [ ] Alternatively (or additionally), remove the `if not v: return "local"` silent-coercion branch from `validate_namespace()` and replace it with an explicit `ValueError` for empty namespace strings - [ ] Write a Behave unit test scenario (in `features/`) that asserts `NamespacedName.parse("/my-action")` raises `ValueError` - [ ] Write a Behave unit test scenario confirming `NamespacedName.parse("my-action")` (no slash) still returns `local/my-action` correctly - [ ] Audit all call sites of `NamespacedName.parse()` in the codebase to confirm none pass leading-slash strings intentionally - [ ] Run `nox -e typecheck` and confirm no Pyright errors are introduced - [ ] Run `nox -e unit_tests` and confirm all scenarios pass - [ ] Run `nox` (all default sessions) and fix any errors - [ ] Verify coverage >= 97% via `nox -e coverage_report` ## Definition of Done - [ ] `NamespacedName.parse("/my-action")` raises `ValueError` with a descriptive message - [ ] `NamespacedName.parse("/my-action")` behaviour is consistent with `parse_namespaced_name("/my-action")` in `project.py` - [ ] `NamespacedName.parse("my-action")` (bare name, no slash) continues to return `local/my-action` correctly - [ ] A Behave feature scenario covers the leading-slash rejection case - [ ] A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly, followed by a blank line, then additional lines providing relevant details - [ ] The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly - [ ] The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done - [ ] All nox stages pass - [ ] Coverage >= 97% --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.7.0 milestone 2026-04-03 04:33:45 +00:00
freemo self-assigned this 2026-04-03 16:58:00 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Blocks
#362 Epic: Security & Safety Hardening
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#2160
No description provided.