Implement addressable artifacts with content-based identity and change detection. Core Features: - Artifact model with SHA-256 content digests - ArtifactReference for cross-space addressing - IArtifactRepository interface for pluggable storage - SQLiteArtifactRepository implementation - ArtifactService for high-level operations - Content digest calculation utilities Database: - prompt_artifacts table with indexes - Support for artifact metadata and types - UNIQUE constraint on space_id+name Tests (41 passing): - 26 model tests (metadata, artifacts, references, digests) - 15 repository tests (CRUD, queries, constraints) Implements: - FR-1.1: Unique addressability by name and ID - FR-1.2: Content digest computation and storage - FR-1.3: Cross-space artifact references Files Created: - markitect/prompts/models.py - markitect/prompts/repositories/interfaces.py - markitect/prompts/repositories/sqlite.py - markitect/prompts/services/artifact_service.py - migrations/prompts/001_create_artifacts_table.sql - tests/unit/prompts/test_artifact_models.py - tests/unit/prompts/test_artifact_repository.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
3.6 KiB
Python
160 lines
3.6 KiB
Python
"""
|
|
Repository interfaces for artifact persistence.
|
|
|
|
Defines abstract interfaces for artifact storage, enabling
|
|
pluggable storage backends.
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import List, Optional
|
|
from markitect.prompts.models import Artifact, ArtifactType
|
|
|
|
|
|
class RepositoryError(Exception):
|
|
"""Base exception for repository errors."""
|
|
pass
|
|
|
|
|
|
class ArtifactNotFoundError(RepositoryError):
|
|
"""Raised when an artifact cannot be found."""
|
|
pass
|
|
|
|
|
|
class DuplicateArtifactError(RepositoryError):
|
|
"""Raised when attempting to create an artifact with duplicate name."""
|
|
pass
|
|
|
|
|
|
class IArtifactRepository(ABC):
|
|
"""
|
|
Abstract interface for artifact persistence.
|
|
|
|
Implements FR-1: InformationSpace Addressability
|
|
Provides CRUD operations for artifacts with content digest tracking.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def create(self, artifact: Artifact) -> Artifact:
|
|
"""
|
|
Persist a new artifact.
|
|
|
|
Args:
|
|
artifact: Artifact to create
|
|
|
|
Returns:
|
|
Created artifact
|
|
|
|
Raises:
|
|
DuplicateArtifactError: If artifact with same space_id+name exists
|
|
RepositoryError: On other persistence errors
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_by_id(self, artifact_id: str) -> Optional[Artifact]:
|
|
"""
|
|
Retrieve artifact by ID.
|
|
|
|
Args:
|
|
artifact_id: Artifact identifier
|
|
|
|
Returns:
|
|
Artifact if found, None otherwise
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_by_name(self, space_id: str, name: str) -> Optional[Artifact]:
|
|
"""
|
|
Retrieve artifact by space and name.
|
|
|
|
Implements FR-1.3: Cross-space artifact lookup
|
|
|
|
Args:
|
|
space_id: Space identifier
|
|
name: Artifact name
|
|
|
|
Returns:
|
|
Artifact if found, None otherwise
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_by_digest(self, content_digest: str) -> List[Artifact]:
|
|
"""
|
|
Find artifacts with matching content digest.
|
|
|
|
Implements FR-1.2: Content digest queries
|
|
|
|
Args:
|
|
content_digest: SHA-256 digest to match
|
|
|
|
Returns:
|
|
List of artifacts with matching digest
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def list_by_space(
|
|
self,
|
|
space_id: str,
|
|
artifact_type: Optional[ArtifactType] = None,
|
|
) -> List[Artifact]:
|
|
"""
|
|
List all artifacts in a space.
|
|
|
|
Args:
|
|
space_id: Space identifier
|
|
artifact_type: Optional type filter
|
|
|
|
Returns:
|
|
List of artifacts in space
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def update(self, artifact: Artifact) -> Artifact:
|
|
"""
|
|
Update an existing artifact.
|
|
|
|
Updates content digest and modified timestamp.
|
|
|
|
Args:
|
|
artifact: Artifact with updated data
|
|
|
|
Returns:
|
|
Updated artifact
|
|
|
|
Raises:
|
|
ArtifactNotFoundError: If artifact doesn't exist
|
|
RepositoryError: On other persistence errors
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def delete(self, artifact_id: str) -> bool:
|
|
"""
|
|
Delete an artifact.
|
|
|
|
Args:
|
|
artifact_id: Artifact identifier
|
|
|
|
Returns:
|
|
True if deleted, False if not found
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def exists(self, space_id: str, name: str) -> bool:
|
|
"""
|
|
Check if artifact exists.
|
|
|
|
Args:
|
|
space_id: Space identifier
|
|
name: Artifact name
|
|
|
|
Returns:
|
|
True if artifact exists
|
|
"""
|
|
pass
|