feat(plan-correction): implement correction data model and persistence
CI / docker (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 1h0m20s
CI / typecheck (pull_request) Successful in 9m34s
CI / e2e_tests (pull_request) Successful in 11m2s
CI / integration_tests (pull_request) Successful in 17m31s
CI / push-validation (pull_request) Successful in 43s
CI / security (pull_request) Successful in 9m47s
CI / unit_tests (pull_request) Failing after 19m18s
CI / helm (pull_request) Successful in 1m2s
CI / build (pull_request) Successful in 7m36s
CI / quality (pull_request) Successful in 8m51s
CI / lint (pull_request) Successful in 9m19s
CI / coverage (pull_request) Successful in 14m26s

Implement CorrectionRepositoryProtocol in the domain layer as the port for
correction attempt record persistence. The protocol defines CRUD operations
(create, get, list_by_plan, update_state, delete) following the clean
architecture pattern with dependency inversion.

The infrastructure adapter CorrectionAttemptRepository provides SQLAlchemy-backed
persistence with full state transition validation, timezone normalization, and
defensive coercion for corrupted database data. Comprehensive BDD test coverage
via correction_attempt_persistence.feature validates all lifecycle transitions,
edge cases, and error conditions.

This enables the Plan Correction Engine (v3.2.0) to persist correction attempts
durably across operations, supporting both revert and append correction modes
with full audit trail tracking.
This commit is contained in:
2026-04-13 22:14:08 +00:00
parent d20dd9b11d
commit 637e2df1f5
3 changed files with 122 additions and 0 deletions
+2
View File
@@ -7,6 +7,8 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
- **Plan Correction Data Model and Persistence** (#8531): Implemented `CorrectionRepositoryProtocol` in the domain layer as the port for correction attempt record persistence. The protocol defines CRUD operations (create, get, list_by_plan, update_state, delete) following the clean architecture pattern. Infrastructure adapter `CorrectionAttemptRepository` provides SQLAlchemy-backed persistence with full state transition validation, timezone normalization, and defensive coercion for corrupted database data. Comprehensive BDD test coverage via `correction_attempt_persistence.feature` validates all lifecycle transitions, edge cases, and error conditions.
- **Checkpoint Listing and Management CLI Commands** (#8559): Implemented `agents plan checkpoint list <plan-id>` and `agents plan checkpoint delete <checkpoint-id>` commands for the v3.3.0 milestone. The list command displays all checkpoints for a plan with ID, timestamp, type, and reason. The delete command removes checkpoints with confirmation. Both commands support multiple output formats (rich, table, json, yaml).
- **TDD Issue-Capture Test Activation** (#7025): Replaced 234 bare `@skip` tags
@@ -16,6 +16,8 @@ allows persistence backends to be swapped without touching application code.
via ``ActionRepository``
- ``DecisionRepositoryProtocol`` → ``Decision``
via ``DecisionRepository``
- ``CorrectionRepositoryProtocol`` → ``CorrectionAttemptRecord``
via ``CorrectionAttemptRepository``
- ``ProjectRepositoryProtocol`` → ``NamespacedProject``
via ``NamespacedProjectRepository``
@@ -24,6 +26,7 @@ allows persistence backends to be swapped without touching application code.
```python
from cleveragents.domain.repositories import (
ActionRepositoryProtocol,
CorrectionRepositoryProtocol,
DecisionRepositoryProtocol,
LifecyclePlanRepositoryProtocol,
ProjectRepositoryProtocol,
@@ -34,9 +37,11 @@ class MyApplicationService:
self,
plans: LifecyclePlanRepositoryProtocol,
actions: ActionRepositoryProtocol,
corrections: CorrectionRepositoryProtocol,
) -> None:
self._plans = plans
self._actions = actions
self._corrections = corrections
```
All protocols are decorated with ``@runtime_checkable`` so that
@@ -47,6 +52,9 @@ injection containers).
from cleveragents.domain.repositories.action_repository import (
ActionRepositoryProtocol,
)
from cleveragents.domain.repositories.correction_repository import (
CorrectionRepositoryProtocol,
)
from cleveragents.domain.repositories.decision_repository import (
DecisionRepositoryProtocol,
)
@@ -59,6 +67,7 @@ from cleveragents.domain.repositories.project_repository import (
__all__ = [
"ActionRepositoryProtocol",
"CorrectionRepositoryProtocol",
"DecisionRepositoryProtocol",
"LifecyclePlanRepositoryProtocol",
"ProjectRepositoryProtocol",
@@ -0,0 +1,111 @@
"""Domain repository protocol for correction attempts.
Defines the ``CorrectionRepositoryProtocol`` — the port that the application
layer uses to persist and retrieve correction attempt records. Infrastructure
adapters (e.g. the SQLAlchemy-backed ``CorrectionAttemptRepository``) must
satisfy this protocol.
Based on the clean architecture principle described in the specification:
adapters live at the edge; the domain layer defines the contracts.
"""
from __future__ import annotations
from datetime import datetime
from typing import Protocol, runtime_checkable
from cleveragents.domain.models.core.correction import (
CorrectionAttemptRecord,
CorrectionAttemptState,
)
@runtime_checkable
class CorrectionRepositoryProtocol(Protocol):
"""Port for correction attempt record persistence.
All methods that mutate state flush but do **not** commit; the caller
or a Unit-of-Work wrapper is responsible for committing the transaction.
"""
def create(self, record: CorrectionAttemptRecord) -> CorrectionAttemptRecord:
"""Persist a new correction attempt record.
Args:
record: A ``CorrectionAttemptRecord`` domain instance.
Returns:
The persisted ``CorrectionAttemptRecord`` domain object.
Raises:
DuplicateCorrectionAttemptError: If the ID already exists.
DatabaseError: On transient or unexpected DB errors.
"""
...
def get(self, correction_attempt_id: str) -> CorrectionAttemptRecord | None:
"""Retrieve a correction attempt by its ULID.
Args:
correction_attempt_id: ULID string of the correction attempt.
Returns:
The ``CorrectionAttemptRecord`` domain object, or ``None`` if not found.
"""
...
def list_by_plan(self, plan_id: str) -> list[CorrectionAttemptRecord]:
"""Retrieve all correction attempts for a plan, ordered by creation time.
Args:
plan_id: ULID of the plan.
Returns:
List of ``CorrectionAttemptRecord`` domain objects.
"""
...
def update_state(
self,
correction_attempt_id: str,
state: CorrectionAttemptState,
completed_at: datetime | None = None,
new_decision_id: str | None = None,
archived_artifacts_path: str | None = None,
) -> CorrectionAttemptRecord:
"""Update the state and optional fields of a correction attempt.
Args:
correction_attempt_id: ULID of the correction attempt.
state: New lifecycle state (``CorrectionAttemptState`` enum).
completed_at: Optional completion timestamp.
new_decision_id: Optional new decision ID (for append mode).
archived_artifacts_path: Optional archived artifacts path.
Returns:
The updated ``CorrectionAttemptRecord`` domain instance.
Raises:
CorrectionAttemptNotFoundError: If not found.
InvalidCorrectionStateTransitionError: If the state transition
is not allowed.
DatabaseError: On input validation failure, FK violation, or
transient/unexpected DB errors.
"""
...
def delete(self, correction_attempt_id: str) -> bool:
"""Delete a correction attempt by its ULID.
Args:
correction_attempt_id: ULID string of the correction attempt to delete.
Returns:
``True`` if the correction attempt was deleted, ``False`` if not found.
"""
...
__all__ = [
"CorrectionRepositoryProtocol",
]