feat(changeset): persist changesets and diff artifacts #429
No reviewers
Labels
No labels
auto/needs-reevaluation
controller-managed
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
#163 feat(changeset): persist changesets and diff artifacts
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core!429
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/m2-changeset-persistence"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
This PR implements persistent storage for ChangeSet entries and ToolInvocation records, replacing the in-memory stores used during plan execution with SQLite-backed repositories. Prior to this change, ChangeSet data (the record of every file mutation made during a plan's Execute phase) was held only in memory and lost on process restart. This made it impossible to review diffs, inspect artifacts, or audit tool invocations after the fact. With persistence in place,
plan diffandplan statuscan now retrieve changeset data across restarts, and cleanup logic ensures that orphaned artifacts are removed when a plan is cancelled or apply fails.This work is part of the M2 milestone (Actor Graphs + Tool Sources) and addresses the Stage D0 requirement for durable changeset storage with full tool invocation auditability.
Closes #163
Motivation
The Execute phase generates a
SpecChangeSetcontainingChangeEntryrecords (one per file mutation) andToolInvocationrecords (one per tool execution). Without persistence, this data was ephemeral. Three concrete problems drove this change:plan difforplan statusafter a process restart saw empty output because the in-memory changeset was gone.Changes
Database layer (
src/cleveragents/infrastructure/database/)models.py): AddedChangeSetEntryModel(tablechangeset_entries) andToolInvocationModel(tabletool_invocations) with indexed columns forchangeset_id,plan_id,path, andtool_nameto support efficient lookups during diff rendering and artifact display. Both models follow the existing declarative Base pattern established by ADR-007.changeset_repository.py, 455 lines): Three classes implementing the session-factory pattern:ChangeSetEntryRepository-- CRUD operations forChangeEntrypersistence. Methods:save_entry,get_entries_for_changeset,get_entries_for_plan,delete_for_changeset,delete_for_plan. Each method includes argument validation, retry decoration via@database_retry, and proper error wrapping intoDatabaseError.ToolInvocationRepository-- CRUD forToolInvocationrecords. Serialises complex fields (arguments, results, change IDs, resource refs, provider metadata) to JSON text columns. Methods:save_invocation,get_invocations_for_plan,delete_for_plan.SqliteChangeSetStore-- Implements theChangeSetStoreprotocol fromcleveragents.domain.models.core.change. Methods:start,record,get,get_for_plan,summarize,delete_for_plan. This is the entry point used by the service layer.Alembic migration (
alembic/versions/d0_001_changeset_artifacts.py)changeset_entriesandtool_invocationstables with the column definitions matching the ORM models. Adds indexes onchangeset_id,plan_id,path, andtool_name. Includes adowngrade()that drops both tables.Service layer (
src/cleveragents/application/services/plan_apply_service.py)cleanup_changeset()method toPlanApplyService. Delegates toChangeSetStore.delete_for_plan()to remove all persisted changeset entries and invocations for a given plan. Called on plan cancel and apply failure paths to reclaim storage. Validatesplan_idis non-empty and logs the number of records deleted.Documentation (
docs/reference/changeset.md)Tests
features/changeset_persistence.feature+features/steps/changeset_persistence_steps.py): 17 Behave scenarios coveringSqliteChangeSetStorestart/record/get round-trips,get_for_planretrieval, summarize counts,ChangeSetEntryRepositoryCRUD,ToolInvocationRepositoryCRUD, cleanup viaPlanApplyService, and argument validation (rejectingNonesession factories, empty plan IDs, and empty changeset IDs). Step definitions use in-memory SQLite for isolation.robot/changeset_persistence.robot): 5 test cases verifying CLI-level artifact output fields, help text, and exit codes for changeset-related commands.benchmarks/changeset_persistence_bench.py): Benchmarks for single-entry persistence, batch persistence (100 entries), retrieval by changeset ID, retrieval by plan ID, and store cleanup. Uses in-memory SQLite to measure persistence overhead without I/O variance.Changelog
## Unreleaseddescribing the new changeset persistence and diff artifact storage capability, with issue reference(#163).Dependencies
dcd026c3129fe6425f07CONTRIBUTING.md Compliance Review for PR #429
Issues Fixed (pushed to branch)
CHANGELOG.md — Mixed concerns in version header (Atomic Commits violation)
The commit changed
## Unreleased→## v3.0.0, bundling a version release designation with the feature addition. Per CONTRIBUTING.md §Atomic Commits: "Never bundle cosmetic changes with functional changes in the same commit." The## Unreleasedheader has been restored. The version designation should be done in a separate, dedicated commit when v3.0.0 is actually released.CHANGELOG.md — Missing issue reference
The new changelog entry did not include an issue reference
(#163), which all other entries in the file consistently use. Added(#163)for consistency.Issue #163 — Ref field was empty
Per CONTRIBUTING.md §Creating Issues: "the Ref field must be set to the same branch named in the issue body's Metadata section." The Metadata section specifies
feature/m2-changeset-persistencebut the Ref field was blank. Updated the issue Ref field tofeature/m2-changeset-persistence.Issues Requiring Author Attention
Commit message footer format
The first commit uses
Closes #163as the footer reference. CONTRIBUTING.md §Commit Message Format specifies theISSUES CLOSED: #163format (see the examples in that section). WhileCloses #163is a valid Forgejo closing keyword, the project convention isISSUES CLOSED: #163. This cannot be fixed without rewriting history; please use the correct format in future commits.# type: ignore[misc]on new model classes (models.pylines 2474, 2508)CONTRIBUTING.md §Type Safety states: "never use inline comments or annotations to suppress individual type checking errors (e.g., no
type: ignore)." The two new model classes (ChangeSetEntryModel,ToolInvocationModel) both use# type: ignore[misc]. This follows the existing codebase pattern (20+ existing models use the same suppression due todeclarative_base()returningAny), so it is a pre-existing codebase-wide issue. However, new code should ideally not perpetuate violations. A separate task should be created to migrate theBasedefinition to SQLAlchemy 2.0'sDeclarativeBaseto eliminate all# type: ignore[misc]suppressions.Compliance Summary
Closes #163)Type/Featurelabelfeatures/robot/benchmarks/docs/State/In Reviewlabel4b6a6e44782d9d751d792d9d751d79e3fcce413b