TDD: SqliteChangeSetStore.get_for_plan() returns all plan entries as a single ChangeSet with a random ID, losing changeset grouping #10401

Open
opened 2026-04-18 09:30:46 +00:00 by HAL9000 · 0 comments
Owner

Metadata

  • Branch: tdd/mX-changeset-store-get-for-plan-grouping
  • Commit Message: test(changeset): add tdd test for SqliteChangeSetStore.get_for_plan() changeset grouping bug

Background and Context

SqliteChangeSetStore.get_for_plan() in src/cleveragents/infrastructure/database/changeset_repository.py (lines ~350–365) returns all ChangeEntry records for a plan as a single SpecChangeSet with a new randomly-generated changeset_id:

def get_for_plan(self, plan_id: str) -> list[SpecChangeSet]:
    entries = self._entry_repo.get_entries_for_plan(plan_id)
    if not entries:
        return []
    return [
        SpecChangeSet(plan_id=plan_id, entries=entries),  # BUG: single changeset, random ID
    ]

When a plan has entries from multiple changesets (e.g., two separate tool invocation batches), this method:

  1. Merges all entries into one SpecChangeSet (losing changeset boundaries)
  2. Assigns a new random ULID as changeset_id (not the actual stored changeset IDs)

The SpecChangeSet model's changeset_id field has default_factory=_new_ulid, so omitting it silently generates a random ID that does not correspond to any real changeset in the database.

A failing BDD test must be written to capture this bug before it is fixed.

Expected Behavior

SqliteChangeSetStore.get_for_plan(plan_id) should return one SpecChangeSet per distinct changeset stored for that plan, with each SpecChangeSet.changeset_id matching the original changeset ID returned by start().

Acceptance Criteria

  • A Behave scenario exists that proves get_for_plan() incorrectly merges multiple changesets into one
  • The scenario is tagged @tdd_issue, @tdd_issue_<N>, and @tdd_expected_fail
  • CI passes with the tag present (test framework inverts the result)

Subtasks

  • Add a Behave scenario in features/steps/changeset_repository_steps.py (or create features/changeset_repository.feature) that:
    • Creates two changesets for the same plan via SqliteChangeSetStore.start() and SqliteChangeSetStore.record()
    • Calls SqliteChangeSetStore.get_for_plan(plan_id)
    • Asserts that the result contains two SpecChangeSet objects (one per changeset)
    • Asserts that each returned SpecChangeSet.changeset_id matches the original changeset IDs returned by start()
  • Tag the scenario with @tdd_issue, @tdd_issue_<N> (where N is the bug issue number), and @tdd_expected_fail
  • Verify the test fails with the current implementation (proving the bug exists)
  • Verify the test passes CI with @tdd_expected_fail tag present

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, followed by a blank line, then additional lines providing relevant details about the implementation.
  • 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.

Automated by CleverAgents Bot
Agent: new-issue-creator


Automated by CleverAgents Bot
Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor
Tag: [AUTO-BUG-4]

## Metadata - **Branch:** `tdd/mX-changeset-store-get-for-plan-grouping` - **Commit Message:** `test(changeset): add tdd test for SqliteChangeSetStore.get_for_plan() changeset grouping bug` ## Background and Context `SqliteChangeSetStore.get_for_plan()` in `src/cleveragents/infrastructure/database/changeset_repository.py` (lines ~350–365) returns all `ChangeEntry` records for a plan as a single `SpecChangeSet` with a new randomly-generated `changeset_id`: ```python def get_for_plan(self, plan_id: str) -> list[SpecChangeSet]: entries = self._entry_repo.get_entries_for_plan(plan_id) if not entries: return [] return [ SpecChangeSet(plan_id=plan_id, entries=entries), # BUG: single changeset, random ID ] ``` When a plan has entries from multiple changesets (e.g., two separate tool invocation batches), this method: 1. Merges all entries into one `SpecChangeSet` (losing changeset boundaries) 2. Assigns a new random ULID as `changeset_id` (not the actual stored changeset IDs) The `SpecChangeSet` model's `changeset_id` field has `default_factory=_new_ulid`, so omitting it silently generates a random ID that does not correspond to any real changeset in the database. A failing BDD test must be written to capture this bug before it is fixed. ## Expected Behavior `SqliteChangeSetStore.get_for_plan(plan_id)` should return one `SpecChangeSet` per distinct changeset stored for that plan, with each `SpecChangeSet.changeset_id` matching the original changeset ID returned by `start()`. ## Acceptance Criteria - A Behave scenario exists that proves `get_for_plan()` incorrectly merges multiple changesets into one - The scenario is tagged `@tdd_issue`, `@tdd_issue_<N>`, and `@tdd_expected_fail` - CI passes with the tag present (test framework inverts the result) ## Subtasks - [ ] Add a Behave scenario in `features/steps/changeset_repository_steps.py` (or create `features/changeset_repository.feature`) that: - Creates two changesets for the same plan via `SqliteChangeSetStore.start()` and `SqliteChangeSetStore.record()` - Calls `SqliteChangeSetStore.get_for_plan(plan_id)` - Asserts that the result contains **two** `SpecChangeSet` objects (one per changeset) - Asserts that each returned `SpecChangeSet.changeset_id` matches the original changeset IDs returned by `start()` - [ ] Tag the scenario with `@tdd_issue`, `@tdd_issue_<N>` (where N is the bug issue number), and `@tdd_expected_fail` - [ ] Verify the test fails with the current implementation (proving the bug exists) - [ ] Verify the test passes CI with `@tdd_expected_fail` tag present ## 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, followed by a blank line, then additional lines providing relevant details about the implementation. - 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. --- **Automated by CleverAgents Bot** Agent: new-issue-creator --- Automated by CleverAgents Bot Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor Tag: [AUTO-BUG-4]
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.

Dependencies

No dependencies set.

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