UAT: DecisionService.record_decision allows multiple prompt_definition decisions per plan — violates spec "Single root" structural invariant #1815

Open
opened 2026-04-02 23:55:16 +00:00 by freemo · 1 comment
Owner

Metadata

  • Branch: bugfix/m3-decision-service-single-root-invariant
  • Commit Message: fix(decisions): enforce single-root invariant in DecisionService.record_decision
  • Milestone: v3.2.0
  • Parent Epic: #357

Background and Context

The decision tree maintained by DecisionService must satisfy the "Single root" structural invariant defined in docs/specification.md: exactly one prompt_definition decision with parent_decision_id IS NULL may exist per plan. This invariant is foundational to the correctness of the decision tree — multiple roots would corrupt the tree structure and break all downstream operations that traverse or render it (e.g., agents plan tree, agents plan explain, correction modes).

This bug was discovered during UAT testing of the Decision and Validation System (v3.4.0 feature area).

Current Behavior

DecisionService.record_decision() silently allows recording a second (or subsequent) prompt_definition decision for the same plan without raising any error. Both decisions are persisted with parent_decision_id IS NULL, creating multiple roots and violating the structural invariant.

Reproduction steps:

from cleveragents.application.services.decision_service import DecisionService
from cleveragents.domain.models.core.decision import DecisionType
from ulid import ULID

svc = DecisionService()
plan_id = str(ULID())

# First prompt_definition — OK
d1 = svc.record_decision(
    plan_id=plan_id,
    decision_type=DecisionType.PROMPT_DEFINITION,
    question='Root prompt 1',
    chosen_option='Do something',
)
# seq=0, succeeds as expected

# Second prompt_definition — should raise but does NOT
d2 = svc.record_decision(
    plan_id=plan_id,
    decision_type=DecisionType.PROMPT_DEFINITION,
    question='Root prompt 2',
    chosen_option='Do something else',
)
# seq=1, also succeeds — VIOLATES SPEC

Code location: cleveragents.application.services.decision_service.DecisionService.record_decision

Expected Behavior

When record_decision() is called with decision_type=DecisionType.PROMPT_DEFINITION for a plan that already has a prompt_definition decision (i.e., a root already exists), the method must raise a BusinessRuleViolation (or a dedicated DuplicateRootDecisionError) before any persistence occurs.

The guard should be inserted before self._store_decision(decision) in cleveragents.application.services.decision_service.DecisionService.record_decision, querying whether a prompt_definition decision already exists for the given plan_id.

Acceptance Criteria

  • Calling DecisionService.record_decision() with DecisionType.PROMPT_DEFINITION for a plan that already has a root decision raises BusinessRuleViolation (or equivalent domain exception).
  • The first prompt_definition decision for a plan is still accepted without error.
  • All other decision_type values are unaffected by this guard.
  • The raised exception message clearly identifies the violation (e.g., "Plan already has a root prompt_definition decision").
  • No second prompt_definition record is persisted when the guard fires.

Supporting Information

  • Spec reference: docs/specification.md — "Structural Invariants" → "Single root: Exactly one prompt_definition decision with parent_decision_id IS NULL."
  • Affected method: cleveragents.application.services.decision_service.DecisionService.record_decision
  • This bug was reported by the UAT tester agent (ca-uat-tester) during UAT testing of the Decision and Validation System.

Subtasks

  • Add guard in cleveragents.application.services.decision_service.DecisionService.record_decision to check for existing root decision before persisting
  • Raise BusinessRuleViolation (or new DuplicateRootDecisionError) with a descriptive message when a duplicate root is detected
  • Tests (Behave): Add BDD scenario @tdd_issue @tdd_issue_<N> — "Given a plan with an existing prompt_definition decision, When record_decision is called with PROMPT_DEFINITION, Then a BusinessRuleViolation is raised"
  • Tests (Behave): Add BDD scenario confirming first prompt_definition still succeeds
  • Tests (Behave): Add BDD scenario confirming other decision types are unaffected
  • Tests (Robot): Add integration test verifying the invariant is enforced end-to-end
  • Verify coverage >= 97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly (fix(decisions): enforce single-root invariant in DecisionService.record_decision), followed by a blank line, then additional lines providing relevant implementation details.
  • The commit is pushed to the remote on the branch bugfix/m3-decision-service-single-root-invariant.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.
  • The companion TDD issue (created alongside this bug issue) has been resolved first, and its @tdd_expected_fail tag has been removed from the test.
  • All nox stages pass.
  • Coverage >= 97%.

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

## Metadata - **Branch**: `bugfix/m3-decision-service-single-root-invariant` - **Commit Message**: `fix(decisions): enforce single-root invariant in DecisionService.record_decision` - **Milestone**: v3.2.0 - **Parent Epic**: #357 ## Background and Context The decision tree maintained by `DecisionService` must satisfy the **"Single root" structural invariant** defined in `docs/specification.md`: exactly one `prompt_definition` decision with `parent_decision_id IS NULL` may exist per plan. This invariant is foundational to the correctness of the decision tree — multiple roots would corrupt the tree structure and break all downstream operations that traverse or render it (e.g., `agents plan tree`, `agents plan explain`, correction modes). This bug was discovered during UAT testing of the Decision and Validation System (v3.4.0 feature area). ## Current Behavior `DecisionService.record_decision()` silently allows recording a second (or subsequent) `prompt_definition` decision for the same plan without raising any error. Both decisions are persisted with `parent_decision_id IS NULL`, creating multiple roots and violating the structural invariant. **Reproduction steps:** ```python from cleveragents.application.services.decision_service import DecisionService from cleveragents.domain.models.core.decision import DecisionType from ulid import ULID svc = DecisionService() plan_id = str(ULID()) # First prompt_definition — OK d1 = svc.record_decision( plan_id=plan_id, decision_type=DecisionType.PROMPT_DEFINITION, question='Root prompt 1', chosen_option='Do something', ) # seq=0, succeeds as expected # Second prompt_definition — should raise but does NOT d2 = svc.record_decision( plan_id=plan_id, decision_type=DecisionType.PROMPT_DEFINITION, question='Root prompt 2', chosen_option='Do something else', ) # seq=1, also succeeds — VIOLATES SPEC ``` **Code location:** `cleveragents.application.services.decision_service.DecisionService.record_decision` ## Expected Behavior When `record_decision()` is called with `decision_type=DecisionType.PROMPT_DEFINITION` for a plan that already has a `prompt_definition` decision (i.e., a root already exists), the method must raise a `BusinessRuleViolation` (or a dedicated `DuplicateRootDecisionError`) before any persistence occurs. The guard should be inserted before `self._store_decision(decision)` in `cleveragents.application.services.decision_service.DecisionService.record_decision`, querying whether a `prompt_definition` decision already exists for the given `plan_id`. ## Acceptance Criteria - [ ] Calling `DecisionService.record_decision()` with `DecisionType.PROMPT_DEFINITION` for a plan that already has a root decision raises `BusinessRuleViolation` (or equivalent domain exception). - [ ] The first `prompt_definition` decision for a plan is still accepted without error. - [ ] All other `decision_type` values are unaffected by this guard. - [ ] The raised exception message clearly identifies the violation (e.g., "Plan already has a root prompt_definition decision"). - [ ] No second `prompt_definition` record is persisted when the guard fires. ## Supporting Information - Spec reference: `docs/specification.md` — "Structural Invariants" → "Single root: Exactly one `prompt_definition` decision with `parent_decision_id IS NULL`." - Affected method: `cleveragents.application.services.decision_service.DecisionService.record_decision` - This bug was reported by the UAT tester agent (ca-uat-tester) during UAT testing of the Decision and Validation System. ## Subtasks - [ ] Add guard in `cleveragents.application.services.decision_service.DecisionService.record_decision` to check for existing root decision before persisting - [ ] Raise `BusinessRuleViolation` (or new `DuplicateRootDecisionError`) with a descriptive message when a duplicate root is detected - [ ] Tests (Behave): Add BDD scenario `@tdd_issue @tdd_issue_<N>` — "Given a plan with an existing prompt_definition decision, When record_decision is called with PROMPT_DEFINITION, Then a BusinessRuleViolation is raised" - [ ] Tests (Behave): Add BDD scenario confirming first `prompt_definition` still succeeds - [ ] Tests (Behave): Add BDD scenario confirming other decision types are unaffected - [ ] Tests (Robot): Add integration test verifying the invariant is enforced end-to-end - [ ] Verify coverage >= 97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly (`fix(decisions): enforce single-root invariant in DecisionService.record_decision`), followed by a blank line, then additional lines providing relevant implementation details. - The commit is pushed to the remote on the branch `bugfix/m3-decision-service-single-root-invariant`. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. - The companion TDD issue (created alongside this bug issue) has been resolved first, and its `@tdd_expected_fail` tag has been removed from the test. - All nox stages pass. - Coverage >= 97%. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
freemo added this to the v3.2.0 milestone 2026-04-02 23:56:50 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • MoSCoW: MoSCoW/Should Have — bug or spec compliance issue.

Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: ca-project-owner

Issue triaged by project owner: - **State**: Verified - **MoSCoW**: MoSCoW/Should Have — bug or spec compliance issue. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: ca-project-owner
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.

Reference
cleveragents/cleveragents-core#1815
No description provided.