An environment for inquiry - complete documentation
This document specifies mandatory design patterns for Veritheia implementation. Each pattern enforces architectural principles preventing system-generated insights while maintaining extensibility. Patterns derive from Domain-Driven Design adapted for epistemic infrastructure requirements.
Documentation serves as the authoritative specification. Each requirement exists in exactly one location. Implementation code includes explicit references to documentation sections. Deviations require documented rationale and approval.
Patterns preserve user intellectual sovereignty through journey-aware data access and provenance tracking. Performance optimizations cannot compromise formation principles.
// User is an aggregate root with Journey as part of the aggregate
public class User : BaseEntity
{
public string Email { get; private set; }
public Persona Persona { get; private set; }
public IReadOnlyCollection<Journey> Journeys => _journeys.AsReadOnly();
private readonly List<Journey> _journeys = new();
// All journey creation goes through the aggregate root
public Journey StartJourney(Process process, string purpose)
{
var journey = new Journey(this, process, purpose);
_journeys.Add(journey);
return journey;
}
}
// Immutable value object for embedding vectors
public record EmbeddingVector
{
public float[] Values { get; }
public string Model { get; }
public int Dimensions => Values.Length;
public EmbeddingVector(float[] values, string model)
{
Values = values ?? throw new ArgumentNullException(nameof(values));
Model = model ?? throw new ArgumentNullException(nameof(model));
if (values.Length != 1536)
throw new ArgumentException("Embeddings must be 1536 dimensions");
}
}
public interface IJourneyAwareRepository<T> where T : BaseEntity
{
// All queries filtered through journey context
Task<T?> GetByIdForJourneyAsync(Ulid id, Ulid journeyId, CancellationToken cancellationToken = default);
Task<IReadOnlyList<T>> ListForJourneyAsync(Ulid journeyId, ISpecification<T> spec, CancellationToken cancellationToken = default);
Task<T> AddToJourneyAsync(T entity, Ulid journeyId, string encounterContext, CancellationToken cancellationToken = default);
Task UpdateInJourneyContextAsync(T entity, Ulid journeyId, CancellationToken cancellationToken = default);
Task<int> CountForJourneyAsync(Ulid journeyId, ISpecification<T> spec, CancellationToken cancellationToken = default);
}
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
Expression<Func<T, object>>? OrderBy { get; }
Expression<Func<T, object>>? OrderByDescending { get; }
int Take { get; }
int Skip { get; }
bool IsPagingEnabled { get; }
}
// Example usage
public class ActiveJourneysSpecification : BaseSpecification<Journey>
{
public ActiveJourneysSpecification(Ulid userId)
{
AddCriteria(j => j.UserId == userId && j.CompletedAt == null);
AddInclude(j => j.JournalEntries);
ApplyOrderByDescending(j => j.CreatedAt);
}
}
public class AuthoredResult<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string Error { get; }
public Ulid JourneyId { get; } // Every result tied to journey
public DateTime AuthoredAt { get; } // When this understanding emerged
public JournalEntry[] GeneratedNarratives { get; } // New insights to record
protected AuthoredResult(bool isSuccess, T? value, string error, Ulid journeyId)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
JourneyId = journeyId;
AuthoredAt = DateTime.UtcNow;
GeneratedNarratives = Array.Empty<JournalEntry>();
}
public static AuthoredResult<T> Success(T value, Ulid journeyId, params JournalEntry[] narratives)
=> new(true, value, string.Empty, journeyId) { GeneratedNarratives = narratives };
public static AuthoredResult<T> Failure(string error, Ulid journeyId)
=> new(false, default, error, journeyId);
}
// Usage in services
public async Task<Result<Document>> ProcessDocumentAsync(string path)
{
if (!File.Exists(path))
return Result<Document>.Failure("File not found");
try
{
var document = await ExtractDocumentAsync(path);
return Result<Document>.Success(document);
}
catch (Exception ex)
{
return Result<Document>.Failure($"Processing failed: {ex.Message}");
}
}
public class ProcessContext
{
public User Author { get; } // Not "User" - they are authors
public Journey IntellectualJourney { get; } // Not just "Journey"
public IReadOnlyList<JournalEntry> FormativeNarratives { get; } // Not just "entries"
public ConceptualFramework AuthorsFramework { get; } // Not generic "scope"
public IReadOnlyDictionary<string, object> AuthoredInputs { get; } // User's specific framing
public CancellationToken CancellationToken { get; }
public ProcessContext(
User user,
Journey journey,
IEnumerable<JournalEntry> relevantEntries,
KnowledgeScope scope,
Dictionary<string, object> inputs,
CancellationToken cancellationToken = default)
{
User = user ?? throw new ArgumentNullException(nameof(user));
Journey = journey ?? throw new ArgumentNullException(nameof(journey));
RelevantEntries = relevantEntries?.ToList() ?? new List<JournalEntry>();
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Inputs = new ReadOnlyDictionary<string, object>(inputs ?? new Dictionary<string, object>());
CancellationToken = cancellationToken;
}
}
public interface ICognitiveAdapter
{
Task<EmbeddingVector> CreateEmbeddingsAsync(
string text,
CancellationToken cancellationToken = default);
Task<FormationAssistance> PerformAssessmentAsync(
string prompt,
ProcessContext context,
AssistantRole role, // Librarian, PeerReviewer, etc.
CancellationToken cancellationToken = default);
Task<bool> IsHealthyAsync(CancellationToken cancellationToken = default);
int MaxContextTokens { get; }
string ModelIdentifier { get; }
}
public enum AssessmentRole
{
Librarian, // For relevance assessment
PeerReviewer, // For contribution assessment
Instructor, // For rubric-based evaluation
Evaluator // For performance measurement
}
public class AssessmentResult
{
public string Content { get; set; }
public AssessmentRole Role { get; set; }
public Dictionary<string, object> Metadata { get; set; } // Scores, rationales, etc.
public Ulid JourneyId { get; set; }
public DateTime AssessedAt { get; set; }
}
// Example implementation
public class SemanticKernelAdapter : ICognitiveAdapter
{
private readonly Kernel _kernel;
private readonly ITextEmbeddingGenerationService _embeddingService;
public int MaxContextTokens => 128000; // Claude 3 context window
public string ModelIdentifier => "claude-3-opus-20240229";
public async Task<EmbeddingVector> CreateEmbeddingsAsync(
string text,
CancellationToken cancellationToken = default)
{
var embeddings = await _embeddingService.GenerateEmbeddingAsync(
text,
cancellationToken: cancellationToken);
return new EmbeddingVector(
embeddings.ToArray(),
"text-embedding-3-small");
}
}
public interface IUnitOfWork : IDisposable
{
IRepository<User> Users { get; }
IRepository<Document> Documents { get; }
IRepository<Journey> Journeys { get; }
IRepository<ProcessExecution> ProcessExecutions { get; }
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
Task BeginTransactionAsync(CancellationToken cancellationToken = default);
Task CommitTransactionAsync(CancellationToken cancellationToken = default);
Task RollbackTransactionAsync(CancellationToken cancellationToken = default);
}
// Usage in services
public async Task<Result<Journey>> CreateJourneyAsync(CreateJourneyCommand command)
{
await _unitOfWork.BeginTransactionAsync();
try
{
var user = await _unitOfWork.Users.GetByIdAsync(command.UserId);
if (user == null)
return Result<Journey>.Failure("User not found");
var journey = user.StartJourney(command.Process, command.Purpose);
await _unitOfWork.SaveChangesAsync();
await _unitOfWork.CommitTransactionAsync();
return Result<Journey>.Success(journey);
}
catch
{
await _unitOfWork.RollbackTransactionAsync();
throw;
}
}
// Commands modify state
public record CreateDocumentCommand(
string FilePath,
string Title,
Ulid ScopeId,
Ulid UserId);
public interface ICommandHandler<TCommand>
{
Task<Result> HandleAsync(TCommand command, CancellationToken cancellationToken = default);
}
// Queries read state
public record GetDocumentsBySearchQuery(
string SearchTerm,
Ulid ScopeId,
int PageNumber = 1,
int PageSize = 20);
public interface IQueryHandler<TQuery, TResult>
{
Task<TResult> HandleAsync(TQuery query, CancellationToken cancellationToken = default);
}
// Implementation
public class GetDocumentsBySearchQueryHandler : IQueryHandler<GetDocumentsBySearchQuery, PagedResult<DocumentDto>>
{
private readonly IRepository<Document> _repository;
public async Task<PagedResult<DocumentDto>> HandleAsync(
GetDocumentsBySearchQuery query,
CancellationToken cancellationToken = default)
{
var spec = new DocumentSearchSpecification(query.SearchTerm, query.ScopeId)
.WithPaging(query.PageNumber, query.PageSize);
var documents = await _repository.ListAsync(spec, cancellationToken);
var count = await _repository.CountAsync(spec, cancellationToken);
return new PagedResult<DocumentDto>(
documents.Select(d => d.ToDto()),
count,
query.PageNumber,
query.PageSize);
}
}
public class SystematicScreeningProcess : IFormationProcess
{
private readonly ICognitiveAdapter _cognitiveAdapter;
private readonly IJourneyAwareRepository<Document> _documentRepo;
public async Task<AuthoredResult<ScreeningResults>> ExecuteAsync(
ProcessContext context,
CancellationToken cancellationToken)
{
var results = new List<DocumentAssessment>();
var documents = await _documentRepo.ListForJourneyAsync(
context.IntellectualJourney.Id,
new AllDocumentsSpec());
foreach (var doc in documents)
{
// AI assists as librarian for relevance
var relevanceAssessment = await _cognitiveAdapter.PerformAssessmentAsync(
BuildRelevancePrompt(doc, context.AuthoredInputs["ResearchQuestions"]),
context,
AssistantRole.Librarian,
cancellationToken);
// AI assists as peer reviewer for contribution
var contributionAssessment = await _cognitiveAdapter.PerformAssessmentAsync(
BuildContributionPrompt(doc, context.AuthoredInputs["ResearchQuestions"]),
context,
AssistantRole.PeerReviewer,
cancellationToken);
results.Add(new DocumentAssessment
{
Document = doc,
RelevanceScore = (double)relevanceAssessment.Metadata["score"],
RelevanceRationale = relevanceAssessment.Content,
ContributionScore = (double)contributionAssessment.Metadata["score"],
ContributionRationale = contributionAssessment.Content
});
}
// User will triage these results to form their understanding
var journalEntry = new JournalEntry
{
Type = JournalType.Method,
Narrative = $"Screened {documents.Count} documents. AI assessments measured patterns in relevance vs contribution..."
};
return AuthoredResult<ScreeningResults>.Success(
new ScreeningResults(results),
context.IntellectualJourney.Id,
journalEntry);
}
}
public class ProcessDefinitionBuilder
{
private string _name = string.Empty;
private string _description = string.Empty;
private readonly List<ProcessInput> _inputs = new();
private readonly List<ProcessOutput> _outputs = new();
private ProcessCategory _category = ProcessCategory.Analytical;
public ProcessDefinitionBuilder WithName(string name)
{
_name = name;
return this;
}
public ProcessDefinitionBuilder WithDescription(string description)
{
_description = description;
return this;
}
public ProcessDefinitionBuilder WithInput(string name, Type type, bool required = true)
{
_inputs.Add(new ProcessInput(name, type, required));
return this;
}
public ProcessDefinitionBuilder WithOutput(string name, Type type)
{
_outputs.Add(new ProcessOutput(name, type));
return this;
}
public ProcessDefinitionBuilder InCategory(ProcessCategory category)
{
_category = category;
return this;
}
public ProcessDefinition Build()
{
if (string.IsNullOrWhiteSpace(_name))
throw new InvalidOperationException("Process name is required");
return new ProcessDefinition(
_name,
_description,
_inputs,
_outputs,
_category);
}
}
// Usage
var definition = new ProcessDefinitionBuilder()
.WithName("Systematic Screening")
.WithDescription("Dual assessment of document relevance and contribution")
.WithInput("researchQuestions", typeof(string[]))
.WithInput("definitions", typeof(Dictionary<string, string>), required: false)
.WithOutput("assessments", typeof(IEnumerable<DocumentAssessment>))
.InCategory(ProcessCategory.Analytical)
.Build();
public class DocumentService
{
private readonly ILogger<DocumentService> _logger;
public async Task<Result<Document>> ProcessAsync(string path)
{
using var _ = _logger.BeginScope(new Dictionary<string, object>
{
["FilePath"] = path,
["CorrelationId"] = Guid.NewGuid()
});
_logger.LogInformation("Starting document processing");
try
{
var result = await ProcessDocumentInternalAsync(path);
_logger.LogInformation("Document processed successfully");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Document processing failed");
throw;
}
}
}
public class CreateJourneyCommandValidator : AbstractValidator<CreateJourneyCommand>
{
public CreateJourneyCommandValidator()
{
RuleFor(x => x.UserId)
.NotEmpty()
.WithMessage("User ID is required");
RuleFor(x => x.Purpose)
.NotEmpty()
.WithMessage("Journey purpose is required")
.MaximumLength(500)
.WithMessage("Purpose must not exceed 500 characters");
RuleFor(x => x.ProcessId)
.NotEmpty()
.WithMessage("Process ID is required")
.MustAsync(ProcessExists)
.WithMessage("Process does not exist");
}
private async Task<bool> ProcessExists(Ulid processId, CancellationToken cancellationToken)
{
// Check process registry
return true;
}
}
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (ValidationException ex)
{
await HandleValidationExceptionAsync(context, ex);
}
catch (NotFoundException ex)
{
await HandleNotFoundExceptionAsync(context, ex);
}
catch (Exception ex)
{
await HandleGenericExceptionAsync(context, ex);
}
}
private async Task HandleValidationExceptionAsync(HttpContext context, ValidationException ex)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
var problemDetails = new ValidationProblemDetails
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
Title = "Validation error",
Status = StatusCodes.Status400BadRequest,
Detail = "One or more validation errors occurred",
Instance = context.Request.Path
};
foreach (var error in ex.Errors)
{
problemDetails.Errors.Add(error.PropertyName, new[] { error.ErrorMessage });
}
await context.Response.WriteAsJsonAsync(problemDetails);
}
}
When implementing any feature, verify:
Remember: These patterns are not suggestions—they are requirements. Deviations must be explicitly documented and justified in ARCHITECTURE.md.