Files
cleveragents-core/docs/reference/repositories.md
T
Jeffrey Phillips Freeman 36c571db83
CI / lint (pull_request) Successful in 15s
CI / typecheck (pull_request) Successful in 30s
CI / security (pull_request) Successful in 22s
CI / quality (pull_request) Successful in 16s
CI / integration_tests (pull_request) Successful in 5m12s
CI / build (pull_request) Successful in 16s
CI / lint (push) Successful in 13s
CI / typecheck (push) Successful in 29s
CI / security (push) Successful in 23s
CI / quality (push) Successful in 16s
CI / unit_tests (pull_request) Successful in 13m42s
CI / integration_tests (push) Successful in 4m53s
CI / build (push) Successful in 16s
CI / unit_tests (push) Successful in 15m9s
CI / coverage (pull_request) Successful in 7m58s
CI / coverage (push) Successful in 8m14s
CI / docker (pull_request) Successful in 9s
CI / docker (push) Successful in 8s
feat(repo): add resource repositories
2026-02-16 21:35:54 +00:00

9.2 KiB

Repository Reference

Overview

Repositories implement the persistence contract defined in ADR-007 (Repository Pattern). Each repository targets a single aggregate root and follows the session-factory pattern: public methods obtain their own session from the factory, flush (but do not commit), and delegate transaction control to the caller or a UnitOfWork wrapper.

All public methods are decorated with @database_retry (ADR-033) to handle transient database errors.

LifecyclePlanRepository

Module: src/cleveragents/infrastructure/database/repositories.py

Provides CRUD operations for v3 lifecycle plans (v3_plans table and its child tables: plan_projects, plan_arguments, plan_invariants).

Constructor

LifecyclePlanRepository(session_factory: Callable[[], Session])

Methods

Method Signature Description
create create(plan) -> Plan Persist a new plan via LifecyclePlanModel.from_domain(). Handles child tables. Raises DuplicatePlanError on duplicate plan_id.
get get(plan_id: str) -> Plan | None Retrieve by ULID primary key. Returns domain model or None.
get_by_name get_by_name(namespaced_name: str) -> Plan | None Retrieve by full namespaced name. Returns domain model or None.
update update(plan) -> Plan Update phase, processing_state, timestamps, actor refs, error fields, and replace all child rows. Raises PlanNotFoundError if the plan does not exist.
delete delete(plan_id: str) -> bool Delete plan and cascade to child tables. Returns True if deleted, False if not found.
list_plans list_plans(phase?, processing_state?, action_name?, project_name?, namespace?, limit=100, offset=0) -> list[Plan] Filter and paginate plans. Deterministic ordering by created_at DESC.
count count(phase?, processing_state?) -> int Count matching plans with optional filters.

Filters

The list_plans method supports the following filters, matching CLI flags:

  • phase: Filter by lifecycle phase (action, strategize, execute, apply).
  • processing_state: Filter by processing state (queued, processing, errored, complete, cancelled, applied, constrained).
  • action_name: Filter by the action template name (FK to actions table).
  • project_name: Filter by project name (joins plan_projects child table).
  • namespace: Filter by plan namespace.

Results are always ordered by created_at DESC for deterministic pagination.

Custom Exceptions

Exception Parent Description
DuplicatePlanError DatabaseError Raised when creating a plan with a plan_id that already exists.
PlanNotFoundError DatabaseError Raised when updating or deleting a plan that does not exist.

Session and Transaction Pattern

# Session-factory pattern: each method gets its own session
repo = LifecyclePlanRepository(session_factory=lambda: session)

# All mutations flush but do NOT commit
plan = repo.create(plan_domain_object)
# Caller must commit:
session.commit()

Child Table Handling

  • plan_projects: Stores plan-to-project links with alias and read_only flags.
  • plan_arguments: Stores resolved argument values with stable ordering via position.
  • plan_invariants: Stores invariant constraints with source provenance (plan, action, project, global).

On create, child rows are populated from the domain model. On update, all child rows are replaced (clear + re-add) to ensure consistency.

ActionRepository

Module: src/cleveragents/infrastructure/database/repositories.py

Provides CRUD operations for v3 lifecycle actions (actions table and its child tables: action_arguments, action_invariants).

See ActionRepository class documentation in repositories.py for full method signatures.

Custom Exceptions

Exception Parent Description
DuplicateActionError DatabaseError Raised when creating an action with a name that already exists.
ActionInUseError BusinessRuleViolation Raised when deleting an action still referenced by plans.

Resource Registry Repositories

The resource registry persistence layer is implemented by two repositories in src/cleveragents/infrastructure/database/repositories.py:

ResourceTypeRepository

Persists and retrieves ResourceTypeSpec domain objects backed by the resource_types table.

Method Description
create(resource_type) Persist a new resource type. Raises DuplicateResourceTypeError on conflict.
get(name) Retrieve by primary key (namespaced name string). Returns None if missing.
list_types(namespace?, user_addable?) List with optional namespace and user_addable filters, ordered by name.
update(resource_type) Update mutable fields. Raises ResourceTypeNotFoundError if missing.
delete(name) Delete by name. Raises ResourceTypeHasResourcesError if resources reference the type. Returns False if not found.

Session pattern: Each method obtains its own session from the factory. Mutating methods flush but do not commit.

ResourceRepository

Persists and retrieves Resource domain objects backed by the resources table.

Method Description
create(resource) Persist a new resource. Validates type existence. Raises ResourceTypeNotFoundError or DuplicateResourceError.
get(resource_id) Retrieve by ULID primary key. Returns None if missing.
get_by_name(namespaced_name) Retrieve by unique namespaced name. Returns None if missing.
list_resources(type_name?, namespace?, limit?, offset?) List with filters and pagination.
update(resource) Update metadata. Raises ResourceNotFoundRepoError if missing.
delete(resource_id) Delete by ULID. Raises ResourceHasEdgesError if DAG edges exist.
resolve_namespaced_name(name_or_id) Try name lookup first, then fall back to ULID. Returns None if not found.

Resource Registry Custom Exceptions

Exception Base Purpose
ResourceTypeNotFoundError DatabaseError Resource type lookup failed
ResourceNotFoundRepoError DatabaseError Resource lookup failed
ResourceTypeHasResourcesError BusinessRuleViolation Delete blocked by referencing resources
ResourceHasEdgesError BusinessRuleViolation Delete blocked by DAG edges
DuplicateResourceTypeError DatabaseError Name uniqueness violation
DuplicateResourceError DatabaseError Name uniqueness violation

Usage Example

from sqlalchemy.orm import sessionmaker
from cleveragents.infrastructure.database.repositories import (
    ResourceTypeRepository,
    ResourceRepository,
)

factory = sessionmaker(bind=engine)
rt_repo = ResourceTypeRepository(factory)
res_repo = ResourceRepository(factory)

# Create a resource type
rt_repo.create(my_resource_type_spec)

# Create a resource
res_repo.create(my_resource)

# Resolve by name or ULID
result = res_repo.resolve_namespaced_name("myorg/my-resource")

UnitOfWork Lifecycle Usage

Module: src/cleveragents/infrastructure/database/unit_of_work.py

The UnitOfWork class provides transactional access to all repositories. The UnitOfWorkContext exposes both legacy and v3 lifecycle repositories within a single transaction boundary.

Available Repositories on UnitOfWorkContext

Property Type Description
actions ActionRepository V3 lifecycle action persistence.
lifecycle_plans LifecyclePlanRepository V3 lifecycle plan persistence.
projects ProjectRepository Legacy project persistence.
plans PlanRepository Legacy plan persistence (deprecated; use lifecycle_plans).
contexts ContextRepository Context item persistence.
changes ChangeRepository Change persistence.
debug_attempts DebugAttemptRepository Debug attempt persistence.
actors ActorRepository Actor persistence.

Transaction Pattern

from cleveragents.infrastructure.database.unit_of_work import UnitOfWork

uow = UnitOfWork(database_url="sqlite:///app.db")

# All operations within the context manager are committed atomically
with uow.transaction() as ctx:
    ctx.actions.create(action)
    ctx.lifecycle_plans.create(plan)
    # Both persisted on successful exit; rolled back on exception

Session-Factory Bridging

The v3 repositories (ActionRepository, LifecyclePlanRepository) use a session-factory pattern (Callable[[], Session]), while legacy repositories take a Session directly. UnitOfWorkContext bridges both patterns by providing a _session_factory() method that returns the transaction's session, ensuring all repositories operate within the same transaction boundary.

Cross-Repository Atomicity

Creating an action and a plan in the same transaction guarantees that either both persist or neither does:

with uow.transaction() as ctx:
    ctx.actions.create(action)
    ctx.lifecycle_plans.create(plan)
    # If plan creation fails, the action is also rolled back