Stage 1 Prompt 4: Basic Ash Resources Setup
OBJECTIVE
Implement foundational Ash resources that model DSPy signatures and programs as domain entities with proper lifecycle management, relationships, and actions. These resources must integrate seamlessly with the signature system and adapter pattern while providing a clean interface for ML operations through Ash’s domain modeling capabilities.
COMPLETE IMPLEMENTATION CONTEXT
ASH DOMAIN MODELING ARCHITECTURE
From ashDocs/documentation/tutorials/get-started.md and DSPEX_INTEGRATION_ARCHITECTURE.md:
Core Domain Philosophy:
- Ash resources serve as domain models for ML operations
- DSPy signatures become Ash resources with attributes and actions
- Programs are resources that reference signatures and manage execution
- Domain provides unified interface for all ML operations
- Resources leverage Ash’s lifecycle management and validation
Domain Architecture:
┌─────────────────────────────────────────────────────────────┐
│ DSPex.ML.Domain │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐│
│ │ Signature │ │ Program │ │ Execution ││
│ │ Resource │ │ Resource │ │ Resource ││
│ │ - Module ref │ │ - Signature ref │ │ - Program ref││
│ │ - Input/Output │ │ - Adapter type │ │ - Input/Output│
│ │ - Validation │ │ - Lifecycle │ │ - Status ││
│ └─────────────────┘ └─────────────────┘ └──────────────┘│
│ │
└─────────────────────────────────────────────────────────────┘
COMPLETE DOMAIN SETUP
From STAGE_1_FOUNDATION_IMPLEMENTATION.md:
defmodule DSPex.ML.Domain do
@moduledoc """
ML domain for DSPy resources.
"""
use Ash.Domain
resources do
resource DSPex.ML.Signature
resource DSPex.ML.Program
resource DSPex.ML.Execution
end
end
ASH RESOURCE PATTERNS FROM DOCUMENTATION
From ashDocs/documentation/tutorials/get-started.md:
Basic Resource Structure:
defmodule Helpdesk.Support.Ticket do
use Ash.Resource, domain: Helpdesk.Support
actions do
defaults [:read]
create :open do
accept [:subject]
end
update :close do
accept []
validate attribute_does_not_equal(:status, :closed) do
message "Ticket is already closed"
end
change set_attribute(:status, :closed)
end
end
attributes do
uuid_primary_key :id
attribute :subject, :string do
allow_nil? false
public? true
end
attribute :status, :atom do
constraints [one_of: [:open, :closed]]
default :open
allow_nil? false
end
end
relationships do
belongs_to :representative, Helpdesk.Support.Representative
end
end
Key Patterns:
use Ash.Resource
with domain specification- Actions for resource lifecycle (
create
,read
,update
,destroy
) - Attributes with proper types, constraints, and defaults
- Relationships between resources (
belongs_to
,has_many
) - Validations and changes for business logic
- Public attributes for external access
SIGNATURE RESOURCE IMPLEMENTATION
From STAGE_1_FOUNDATION_IMPLEMENTATION.md with extensions:
defmodule DSPex.ML.Signature do
@moduledoc """
Ash resource for managing DSPy signatures.
"""
use Ash.Resource,
domain: DSPex.ML.Domain,
data_layer: AshPostgres.DataLayer
attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false, public?: true
attribute :module, :string, allow_nil?: false, public?: true
attribute :inputs, {:array, :map}, default: [], public?: true
attribute :outputs, {:array, :map}, default: [], public?: true
attribute :description, :string, public?: true
attribute :version, :string, default: "1.0.0", public?: true
attribute :status, :atom do
constraints [one_of: [:draft, :active, :deprecated]]
default :draft
public? true
end
timestamps()
end
actions do
defaults [:read, :create, :update, :destroy]
create :from_module do
accept [:name, :description, :version]
argument :signature_module, :atom, allow_nil?: false
change fn changeset, _context ->
signature_module = Ash.Changeset.get_argument(changeset, :signature_module)
try do
signature = signature_module.__signature__()
changeset
|> Ash.Changeset.change_attribute(:module, to_string(signature_module))
|> Ash.Changeset.change_attribute(:inputs, signature.inputs)
|> Ash.Changeset.change_attribute(:outputs, signature.outputs)
|> Ash.Changeset.change_attribute(:name,
Ash.Changeset.get_attribute(changeset, :name) || to_string(signature_module))
rescue
error ->
Ash.Changeset.add_error(changeset,
field: :signature_module,
message: "Invalid signature module: #{inspect(error)}")
end
end
end
update :activate do
accept []
validate attribute_equals(:status, :draft) do
message "Only draft signatures can be activated"
end
change set_attribute(:status, :active)
end
update :deprecate do
accept []
validate attribute_equals(:status, :active) do
message "Only active signatures can be deprecated"
end
change set_attribute(:status, :deprecated)
end
action :validate_module, :boolean do
argument :signature_module, :atom, allow_nil?: false
run fn input, _context ->
module = input.arguments.signature_module
try do
case Code.ensure_loaded(module) do
{:module, _} ->
if function_exported?(module, :__signature__, 0) do
_signature = module.__signature__()
{:ok, true}
else
{:ok, false}
end
{:error, _} ->
{:ok, false}
end
rescue
_ -> {:ok, false}
end
end
end
end
relationships do
has_many :programs, DSPex.ML.Program
end
validations do
validate present([:name, :module, :inputs, :outputs])
validate match(:module, ~r/^[A-Z][a-zA-Z0-9_.]*$/) do
message "Module must be a valid Elixir module name"
end
end
code_interface do
define :from_module, args: [:signature_module]
define :activate
define :deprecate
define :validate_module, args: [:signature_module]
end
end
PROGRAM RESOURCE IMPLEMENTATION
From STAGE_1_FOUNDATION_IMPLEMENTATION.md with comprehensive extensions:
defmodule DSPex.ML.Program do
@moduledoc """
Ash resource for managing DSPy programs.
"""
use Ash.Resource,
domain: DSPex.ML.Domain,
data_layer: AshPostgres.DataLayer
attributes do
uuid_primary_key :id
attribute :name, :string, allow_nil?: false, public?: true
attribute :dspy_program_id, :string, public?: true # ID in adapter backend
attribute :adapter_type, :atom do
constraints [one_of: [:python_port, :native, :mock]]
default :python_port
public? true
end
attribute :status, :atom do
constraints [one_of: [:draft, :initializing, :ready, :error, :archived]]
default :draft
public? true
end
attribute :configuration, :map, default: %{}, public?: true
attribute :last_executed_at, :utc_datetime, public?: true
attribute :execution_count, :integer, default: 0, public?: true
attribute :error_message, :string, public?: true
timestamps()
end
relationships do
belongs_to :signature, DSPex.ML.Signature, public?: true
has_many :executions, DSPex.ML.Execution
end
actions do
defaults [:read, :update, :destroy]
create :create_with_signature do
accept [:name, :adapter_type, :configuration]
argument :signature_module, :atom, allow_nil?: false
change fn changeset, _context ->
signature_module = Ash.Changeset.get_argument(changeset, :signature_module)
# Validate signature module
case DSPex.ML.Signature.validate_module(%{signature_module: signature_module}) do
{:ok, true} ->
# Find or create signature record
case find_or_create_signature(signature_module) do
{:ok, signature} ->
changeset
|> Ash.Changeset.manage_relationship(:signature, signature, type: :replace)
{:error, error} ->
Ash.Changeset.add_error(changeset,
field: :signature_module,
message: "Failed to create signature: #{inspect(error)}")
end
{:ok, false} ->
Ash.Changeset.add_error(changeset,
field: :signature_module,
message: "Invalid signature module")
{:error, error} ->
Ash.Changeset.add_error(changeset,
field: :signature_module,
message: "Error validating signature: #{inspect(error)}")
end
end
end
update :initialize do
accept []
validate attribute_equals(:status, :draft) do
message "Only draft programs can be initialized"
end
change set_attribute(:status, :initializing)
change fn changeset, _context ->
# This will be handled by after_action hook
changeset
end
change after_action(fn changeset, program, _context ->
case initialize_program_with_adapter(program) do
{:ok, program_id} ->
{:ok, program
|> Ash.Changeset.for_update(:update, %{
dspy_program_id: program_id,
status: :ready
})
|> Ash.update!()}
{:error, reason} ->
{:ok, program
|> Ash.Changeset.for_update(:update, %{
status: :error,
error_message: to_string(reason)
})
|> Ash.update!()}
end
end)
end
action :execute, :map do
argument :inputs, :map, allow_nil?: false
argument :execution_options, :map, default: %{}
run fn input, context ->
program = context.resource
case program.status do
:ready ->
execute_program_with_tracking(program, input.arguments.inputs, input.arguments.execution_options)
:error ->
{:error, "Program is in error state: #{program.error_message}"}
status ->
{:error, "Program not ready for execution (status: #{status})"}
end
end
end
update :archive do
accept []
change set_attribute(:status, :archived)
end
action :get_stats, :map do
run fn _input, context ->
program = context.resource
{:ok, %{
id: program.id,
name: program.name,
status: program.status,
execution_count: program.execution_count,
last_executed_at: program.last_executed_at,
adapter_type: program.adapter_type,
dspy_program_id: program.dspy_program_id
}}
end
end
end
validations do
validate present([:name])
validate present([:signature])
end
code_interface do
define :create_with_signature, args: [:signature_module]
define :initialize
define :execute, args: [:inputs]
define :archive
define :get_stats
end
# Private helper functions
defp find_or_create_signature(signature_module) do
module_string = to_string(signature_module)
case DSPex.ML.Signature
|> Ash.Query.filter(module == ^module_string)
|> Ash.read_one() do
{:ok, nil} ->
# Create new signature
DSPex.ML.Signature.from_module(%{
signature_module: signature_module,
name: module_string
})
{:ok, signature} ->
{:ok, signature}
{:error, reason} ->
{:error, reason}
end
end
defp initialize_program_with_adapter(program) do
adapter = DSPex.Adapters.Registry.get_adapter(program.adapter_type)
signature_module = String.to_existing_atom(program.signature.module)
config = %{
id: program.id,
signature: signature_module,
modules: Map.get(program.configuration, :modules, [])
}
case adapter.create_program(config) do
{:ok, program_id} -> {:ok, program_id}
{:error, reason} -> {:error, reason}
end
end
defp execute_program_with_tracking(program, inputs, execution_options) do
adapter = DSPex.Adapters.Registry.get_adapter(program.adapter_type)
# Validate inputs against signature
signature_module = String.to_existing_atom(program.signature.module)
case signature_module.validate_inputs(inputs) do
{:ok, validated_inputs} ->
# Execute with adapter
case DSPex.Adapters.Factory.execute_with_adapter(
adapter,
:execute_program,
[program.dspy_program_id, validated_inputs],
Map.to_list(execution_options)
) do
{:ok, outputs} ->
# Update execution tracking
update_execution_tracking(program)
# Create execution record
create_execution_record(program, validated_inputs, outputs)
{:ok, outputs}
{:error, reason} ->
{:error, reason}
end
{:error, validation_error} ->
{:error, "Input validation failed: #{validation_error}"}
end
end
defp update_execution_tracking(program) do
program
|> Ash.Changeset.for_update(:update, %{
execution_count: program.execution_count + 1,
last_executed_at: DateTime.utc_now()
})
|> Ash.update()
end
defp create_execution_record(program, inputs, outputs) do
DSPex.ML.Execution.create(%{
program_id: program.id,
inputs: inputs,
outputs: outputs,
status: :completed,
executed_at: DateTime.utc_now()
})
end
end
EXECUTION RESOURCE FOR TRACKING
Complete Execution Resource:
defmodule DSPex.ML.Execution do
@moduledoc """
Ash resource for tracking program executions.
"""
use Ash.Resource,
domain: DSPex.ML.Domain,
data_layer: AshPostgres.DataLayer
attributes do
uuid_primary_key :id
attribute :inputs, :map, allow_nil?: false, public?: true
attribute :outputs, :map, public?: true
attribute :status, :atom do
constraints [one_of: [:pending, :running, :completed, :failed]]
default :pending
public? true
end
attribute :duration_ms, :integer, public?: true
attribute :error_message, :string, public?: true
attribute :executed_at, :utc_datetime, allow_nil?: false, public?: true
attribute :metadata, :map, default: %{}, public?: true
timestamps()
end
relationships do
belongs_to :program, DSPex.ML.Program, public?: true
end
actions do
defaults [:read, :create, :update]
create :create_execution do
accept [:inputs, :metadata]
argument :program_id, :uuid, allow_nil?: false
change fn changeset, _context ->
program_id = Ash.Changeset.get_argument(changeset, :program_id)
changeset
|> Ash.Changeset.manage_relationship(:program, program_id, type: :replace)
|> Ash.Changeset.change_attribute(:executed_at, DateTime.utc_now())
end
end
update :mark_running do
accept []
change set_attribute(:status, :running)
end
update :mark_completed do
accept [:outputs, :duration_ms]
change set_attribute(:status, :completed)
end
update :mark_failed do
accept [:error_message, :duration_ms]
change set_attribute(:status, :failed)
end
read :by_program do
argument :program_id, :uuid, allow_nil?: false
filter expr(program_id == ^arg(:program_id))
end
read :recent_executions do
argument :limit, :integer, default: 50
prepare fn query, _context ->
limit = Ash.Query.get_argument(query, :limit)
query
|> Ash.Query.sort(executed_at: :desc)
|> Ash.Query.limit(limit)
end
end
end
validations do
validate present([:inputs, :executed_at])
validate present([:program])
end
code_interface do
define :create_execution, args: [:program_id, :inputs]
define :mark_running
define :mark_completed, args: [:outputs, :duration_ms]
define :mark_failed, args: [:error_message, :duration_ms]
define :by_program, args: [:program_id]
define :recent_executions, args: []
end
end
MANUAL ACTIONS FOR ML OPERATIONS
From ashDocs/documentation/topics/actions/manual-actions.md:
Manual Action Patterns for ML Operations:
defmodule DSPex.ML.Actions.ProgramExecution do
@moduledoc """
Manual action for executing ML programs with full lifecycle management.
"""
use Ash.Resource.ManualCreate
def create(changeset, _opts, _context) do
program_id = Ash.Changeset.get_argument(changeset, :program_id)
inputs = Ash.Changeset.get_argument(changeset, :inputs)
options = Ash.Changeset.get_argument(changeset, :execution_options) || %{}
# Load program
case DSPex.ML.Program.get!(program_id) do
program when program.status == :ready ->
execute_with_tracking(program, inputs, options)
program ->
{:error, "Program not ready for execution (status: #{program.status})"}
end
rescue
error ->
{:error, "Execution failed: #{inspect(error)}"}
end
defp execute_with_tracking(program, inputs, options) do
start_time = System.monotonic_time(:millisecond)
# Create execution record
{:ok, execution} = DSPex.ML.Execution.create_execution(%{
program_id: program.id,
inputs: inputs,
metadata: %{
adapter_type: program.adapter_type,
options: options
}
})
# Mark as running
{:ok, execution} = DSPex.ML.Execution.mark_running(execution)
try do
# Execute program
case DSPex.ML.Program.execute(program, %{inputs: inputs, execution_options: options}) do
{:ok, outputs} ->
duration = System.monotonic_time(:millisecond) - start_time
{:ok, execution} = DSPex.ML.Execution.mark_completed(execution, %{
outputs: outputs,
duration_ms: duration
})
{:ok, %{
execution_id: execution.id,
outputs: outputs,
duration_ms: duration,
status: :completed
}}
{:error, reason} ->
duration = System.monotonic_time(:millisecond) - start_time
{:ok, _execution} = DSPex.ML.Execution.mark_failed(execution, %{
error_message: to_string(reason),
duration_ms: duration
})
{:error, reason}
end
rescue
error ->
duration = System.monotonic_time(:millisecond) - start_time
{:ok, _execution} = DSPex.ML.Execution.mark_failed(execution, %{
error_message: "Execution exception: #{inspect(error)}",
duration_ms: duration
})
{:error, error}
end
end
end
DATA LAYER CONFIGURATION
PostgreSQL Configuration:
# config/config.exs
config :dspex, DSPex.Repo,
username: "postgres",
password: "postgres",
hostname: "localhost",
database: "dspex_dev",
pool_size: 10
config :dspex, ecto_repos: [DSPex.Repo]
Repo Module:
defmodule DSPex.Repo do
use AshPostgres.Repo, otp_app: :dspex
def installed_extensions do
["uuid-ossp", "citext"]
end
end
Migration Files:
# priv/repo/migrations/20240101000001_create_signatures.exs
defmodule DSPex.Repo.Migrations.CreateSignatures do
use Ecto.Migration
def up do
create table(:signatures, primary_key: false) do
add :id, :uuid, primary_key: true, default: fragment("gen_random_uuid()")
add :name, :string, null: false
add :module, :string, null: false
add :inputs, {:array, :map}, default: []
add :outputs, {:array, :map}, default: []
add :description, :text
add :version, :string, default: "1.0.0"
add :status, :string, default: "draft"
timestamps()
end
create unique_index(:signatures, [:module])
create index(:signatures, [:name])
create index(:signatures, [:status])
end
def down do
drop table(:signatures)
end
end
COMPREHENSIVE TESTING PATTERNS
Resource Testing:
defmodule DSPex.ML.SignatureTest do
use ExUnit.Case
alias DSPex.ML.Signature
defmodule TestSignature do
use DSPex.Signature
signature question: :string -> answer: :string, confidence: :float
end
setup do
# Reset test database
:ok
end
test "creates signature from module" do
{:ok, signature} = Signature.from_module(%{
signature_module: TestSignature,
name: "Test QA Signature",
description: "A test signature for Q&A"
})
assert signature.name == "Test QA Signature"
assert signature.module == "DSPex.ML.SignatureTest.TestSignature"
assert length(signature.inputs) == 1
assert length(signature.outputs) == 2
assert signature.status == :draft
end
test "validates signature module" do
{:ok, true} = Signature.validate_module(%{signature_module: TestSignature})
{:ok, false} = Signature.validate_module(%{signature_module: NonExistentModule})
end
test "activates draft signature" do
{:ok, signature} = Signature.from_module(%{
signature_module: TestSignature,
name: "Test Signature"
})
{:ok, activated} = Signature.activate(signature)
assert activated.status == :active
end
test "prevents activating non-draft signature" do
{:ok, signature} = Signature.from_module(%{
signature_module: TestSignature,
name: "Test Signature"
})
{:ok, activated} = Signature.activate(signature)
# Try to activate again
assert_raise Ash.Error.Invalid, fn ->
Signature.activate(activated)
end
end
end
defmodule DSPex.ML.ProgramTest do
use ExUnit.Case
alias DSPex.ML.{Program, Signature}
defmodule TestSignature do
use DSPex.Signature
signature question: :string -> answer: :string
end
setup do
# Ensure mock adapter is available
{:ok, _} = DSPex.Adapters.Mock.start_link()
DSPex.Adapters.Mock.reset()
# Set adapter to mock for testing
Application.put_env(:dspex, :adapter, :mock)
:ok
end
test "creates program with signature" do
{:ok, program} = Program.create_with_signature(%{
signature_module: TestSignature,
name: "Test Program",
adapter_type: :mock
})
assert program.name == "Test Program"
assert program.adapter_type == :mock
assert program.status == :draft
assert program.signature.module == "DSPex.ML.ProgramTest.TestSignature"
end
test "initializes program with adapter" do
{:ok, program} = Program.create_with_signature(%{
signature_module: TestSignature,
name: "Test Program",
adapter_type: :mock
})
{:ok, initialized} = Program.initialize(program)
assert initialized.status == :ready
assert initialized.dspy_program_id != nil
end
test "executes program with valid inputs" do
{:ok, program} = Program.create_with_signature(%{
signature_module: TestSignature,
name: "Test Program",
adapter_type: :mock
})
{:ok, program} = Program.initialize(program)
{:ok, result} = Program.execute(program, %{
inputs: %{question: "What is 2+2?"}
})
assert Map.has_key?(result, :answer) or Map.has_key?(result, "answer")
end
test "rejects execution with invalid inputs" do
{:ok, program} = Program.create_with_signature(%{
signature_module: TestSignature,
name: "Test Program",
adapter_type: :mock
})
{:ok, program} = Program.initialize(program)
{:error, reason} = Program.execute(program, %{
inputs: %{wrong_field: "test"}
})
assert reason =~ "validation failed"
end
test "tracks execution statistics" do
{:ok, program} = Program.create_with_signature(%{
signature_module: TestSignature,
name: "Test Program",
adapter_type: :mock
})
{:ok, program} = Program.initialize(program)
# Execute multiple times
for _ <- 1..3 do
Program.execute(program, %{inputs: %{question: "test"}})
end
{:ok, stats} = Program.get_stats(program)
assert stats.execution_count >= 3
assert stats.last_executed_at != nil
end
end
INTEGRATION WITH CODE INTERFACES
From ashDocs/documentation/topics/resources/code-interfaces.md:
Enhanced Code Interfaces:
defmodule DSPex.ML do
@moduledoc """
High-level interface for ML operations.
"""
# Signature operations
defdelegate create_signature_from_module(signature_module, opts \\ %{}),
to: DSPex.ML.Signature, as: :from_module
defdelegate activate_signature(signature),
to: DSPex.ML.Signature, as: :activate
# Program operations
defdelegate create_program(signature_module, opts \\ %{}),
to: DSPex.ML.Program, as: :create_with_signature
defdelegate initialize_program(program),
to: DSPex.ML.Program, as: :initialize
defdelegate execute_program(program, inputs, opts \\ %{}),
to: DSPex.ML.Program, as: :execute
# Convenience functions
def quick_execute(signature_module, inputs, opts \\ %{}) do
adapter_type = Keyword.get(opts, :adapter_type, :mock)
with {:ok, program} <- create_program(signature_module, %{
name: "Quick Execute - #{signature_module}",
adapter_type: adapter_type
}),
{:ok, program} <- initialize_program(program),
{:ok, result} <- execute_program(program, inputs) do
{:ok, result}
end
end
def list_active_signatures do
DSPex.ML.Signature
|> Ash.Query.filter(status == :active)
|> Ash.read!()
end
def get_program_statistics(program_id) do
case DSPex.ML.Execution.by_program(%{program_id: program_id}) do
{:ok, executions} ->
total_executions = length(executions)
successful = Enum.count(executions, &(&1.status == :completed))
failed = Enum.count(executions, &(&1.status == :failed))
avg_duration = executions
|> Enum.filter(&(&1.duration_ms != nil))
|> Enum.map(&(&1.duration_ms))
|> case do
[] -> 0
durations -> Enum.sum(durations) / length(durations)
end
{:ok, %{
total_executions: total_executions,
successful_executions: successful,
failed_executions: failed,
success_rate: if(total_executions > 0, do: successful / total_executions, else: 0),
average_duration_ms: avg_duration
}}
{:error, reason} ->
{:error, reason}
end
end
end
IMPLEMENTATION TASK
Based on the complete context above, implement the foundational Ash resources system with the following specific requirements:
FILE STRUCTURE TO CREATE:
lib/dspex/ml/
├── domain.ex # Main domain definition
├── signature.ex # Signature resource
├── program.ex # Program resource
├── execution.ex # Execution tracking resource
├── actions/
│ ├── program_execution.ex # Manual execution action
│ └── signature_validation.ex # Signature validation action
└── interfaces.ex # High-level code interfaces
priv/repo/migrations/
├── 001_create_signatures.exs # Signature table
├── 002_create_programs.exs # Program table
└── 003_create_executions.exs # Execution table
test/dspex/ml/
├── domain_test.exs # Domain functionality tests
├── signature_test.exs # Signature resource tests
├── program_test.exs # Program resource tests
├── execution_test.exs # Execution resource tests
└── integration_test.exs # Cross-resource integration tests
SPECIFIC IMPLEMENTATION REQUIREMENTS:
Domain Setup (
lib/dspex/ml/domain.ex
):- Complete domain definition with all resources
- Proper resource registration and configuration
- Domain-level policies and authorization setup
- Integration with application configuration
Signature Resource (
lib/dspex/ml/signature.ex
):- Complete resource definition with all attributes
- Actions for lifecycle management (create, activate, deprecate)
- Signature module validation and integration
- Proper relationships and constraints
Program Resource (
lib/dspex/ml/program.ex
):- Comprehensive program lifecycle management
- Integration with adapter pattern for execution
- Execution tracking and statistics
- Error handling and status management
Execution Resource (
lib/dspex/ml/execution.ex
):- Complete execution tracking with timing
- Status management and error recording
- Relationships with programs and metadata
- Query actions for analytics and reporting
Manual Actions (
lib/dspex/ml/actions/
):- Complex execution logic with full lifecycle
- Error handling and recovery patterns
- Performance tracking and monitoring
- Integration with adapter pattern
QUALITY REQUIREMENTS:
- Data Integrity: Proper constraints, validations, and relationships
- Performance: Efficient queries and minimal database overhead
- Reliability: Robust error handling and transaction management
- Usability: Clean code interfaces and intuitive APIs
- Testability: Comprehensive test coverage for all scenarios
- Documentation: Clear documentation for all public APIs
- Migration Safety: Safe database migrations with rollback support
INTEGRATION POINTS:
- Must integrate with signature system for validation
- Should use adapter pattern for execution backend
- Must support configuration-driven data layer selection
- Should provide metrics and monitoring capabilities
- Must handle concurrent operations safely
SUCCESS CRITERIA:
- All resources create and manage data correctly
- Signature integration works seamlessly
- Program lifecycle management functions properly
- Execution tracking captures all necessary data
- Manual actions handle complex workflows
- Code interfaces provide clean high-level APIs
- Database migrations run safely
- All test scenarios pass with comprehensive coverage
- Performance meets requirements for ML workloads
- Integration with other system components works correctly
These Ash resources provide the domain modeling foundation that transforms DSPy operations into properly managed, persistent, and queryable entities within the Ash framework ecosystem.