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
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:
@@ -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",
|
||||
]
|
||||
Reference in New Issue
Block a user