An environment for inquiry - complete documentation
This document specifies the design patterns that emerge from Veritheia’s architectural commitments. These are not patterns imposed from industry fashion but natural consequences of our technology choices and philosophical stance.
We embrace Domain-Driven Design’s ontology—that software should model the problem domain with precision—and its telos—maintaining model integrity through explicit boundaries. However, we reject DDD’s praxis entirely. No repositories hiding the database. No aggregate roots pretending to enforce rules that PostgreSQL already enforces. No value objects when C# records suffice. The schema IS the domain model. The constraints ARE the business rules. Entity Framework Core provides direct projection of database truth into runtime, nothing more.
Every pattern documented here serves the architectural principles established in ARCHITECTURE.md: user sovereignty through partition boundaries, intellectual formation through projection spaces, anti-surveillance through structural design, and testing through real execution rather than mocked abstractions.
Every significant query begins with user_id. This is not a convention but an invariant. The database partitions naturally along user boundaries, and every query must respect this partitioning.
// CORRECT: Query scoped to user partition
var documents = await _db.Documents
.Where(d => d.UserId == userId)
.Where(d => d.UploadedAt > since)
.ToListAsync();
// VIOLATION: Cross-partition query without explicit authorization
var allDocuments = await _db.Documents
.Where(d => d.UploadedAt > since)
.ToListAsync(); // This violates partition boundaries
Cross-partition operations require explicit bridges with audit trails. They are never accidental.
Within a user’s partition, most operations scope further to a specific journey. Documents gain meaning only through journey projection. Segments exist only within journey context. Assessments measure only against journey criteria.
// CORRECT: Journey-scoped query
var segments = await _db.JourneyDocumentSegments
.Where(s => s.JourneyId == journeyId)
.Include(s => s.Document)
.ToListAsync();
// VIOLATION: Accessing segments without journey context
var segments = await _db.JourneyDocumentSegments
.Where(s => s.Document.UserId == userId)
.ToListAsync(); // Segments without journey context are meaningless
Entity Framework Core maps directly to the database schema. No repository abstractions. No unit of work wrappers. The DbContext IS the unit of work. The DbSet IS the repository.
// CORRECT: Direct use of DbContext
public class JourneyService
{
private readonly VeritheiaDbContext _db;
public async Task<Journey> CreateJourney(Guid userId, Guid personaId, string purpose)
{
var journey = new Journey
{
Id = Guid.CreateVersion7(),
UserId = userId,
PersonaId = personaId,
Purpose = purpose,
State = "Active",
CreatedAt = DateTime.UtcNow
};
_db.Journeys.Add(journey);
await _db.SaveChangesAsync(); // Direct transaction
return journey;
}
}
// VIOLATION: Repository abstraction over DbContext
public interface IJourneyRepository
{
Task<Journey> CreateAsync(Journey journey);
}
// This adds no value - DbContext already provides this
Common query patterns become extension methods, not repository methods. This preserves composability while avoiding abstraction.
public static class QueryExtensions
{
// User partition enforcement
public static IQueryable<T> ForUser<T>(
this IQueryable<T> query,
Guid userId) where T : IUserOwned
{
return query.Where(e => e.UserId == userId);
}
// Journey scope enforcement
public static IQueryable<JourneyDocumentSegment> ForJourney(
this IQueryable<JourneyDocumentSegment> segments,
Guid journeyId)
{
return segments.Where(s => s.JourneyId == journeyId);
}
// Projection space navigation
public static IQueryable<JourneyDocumentSegment> WithAssessments(
this IQueryable<JourneyDocumentSegment> segments)
{
return segments.Include(s => s.Assessments);
}
}
// Usage composes naturally
var relevantSegments = await _db.JourneyDocumentSegments
.ForJourney(journeyId)
.WithAssessments()
.Where(s => s.Assessments.Any(a => a.RelevanceScore > 0.7))
.ToListAsync();
Services orchestrate operations within partition and journey boundaries. They use DbContext directly, return domain entities, and let PostgreSQL enforce constraints.
public class DocumentService
{
private readonly VeritheiaDbContext _db;
private readonly IFileStorage _files;
private readonly ILogger<DocumentService> _logger;
public async Task<Document> IngestDocument(
Guid userId,
Stream content,
string fileName,
Guid? scopeId = null)
{
// Validate user exists (FK will enforce)
var userExists = await _db.Users.AnyAsync(u => u.Id == userId);
if (!userExists)
throw new InvalidOperationException($"User {userId} not found");
// Store file content
var storagePath = await _files.StoreAsync(content, fileName);
// Create document in user's partition
var document = new Document
{
Id = Guid.CreateVersion7(),
UserId = userId, // Partition key
ScopeId = scopeId,
FileName = fileName,
FilePath = storagePath,
FileSize = content.Length,
UploadedAt = DateTime.UtcNow
};
_db.Documents.Add(document);
await _db.SaveChangesAsync(); // PostgreSQL enforces all constraints
_logger.LogInformation(
"Document {DocumentId} ingested for user {UserId}",
document.Id, userId);
return document;
}
}
Processes operate within journey projection spaces. The ProcessContext carries all necessary scope information.
public class ProcessContext
{
public Guid UserId { get; init; }
public Guid JourneyId { get; init; }
public Guid PersonaId { get; init; }
public Dictionary<string, object> Parameters { get; init; }
public string JournalContext { get; init; } // Assembled narrative
public CancellationToken CancellationToken { get; init; }
}
public interface IAnalyticalProcess
{
Task<ProcessResult> ExecuteAsync(ProcessContext context);
}
public class SystematicScreeningProcess : IAnalyticalProcess
{
private readonly VeritheiaDbContext _db;
private readonly ICognitiveAdapter _cognitive;
public async Task<ProcessResult> ExecuteAsync(ProcessContext context)
{
// All queries naturally scoped to journey
var segments = await _db.JourneyDocumentSegments
.Where(s => s.JourneyId == context.JourneyId)
.Include(s => s.Document)
.ToListAsync(context.CancellationToken);
// Process within projection space
foreach (var segment in segments)
{
// AI measures within journey's conceptual framework
var assessment = await _cognitive.AssessAsync(
segment.SegmentContent,
context.Parameters["ResearchQuestions"].ToString(),
context.JournalContext);
// Store assessment in journey context
var record = new JourneySegmentAssessment
{
Id = Guid.CreateVersion7(),
SegmentId = segment.Id,
AssessmentType = "Relevance",
Score = assessment.Score,
Reasoning = assessment.Reasoning,
AssessedAt = DateTime.UtcNow
};
_db.JourneySegmentAssessments.Add(record);
}
await _db.SaveChangesAsync();
return new ProcessResult
{
Success = true,
Data = new { SegmentCount = segments.Count }
};
}
}
File storage is external to PostgreSQL, therefore it warrants abstraction.
public interface IFileStorage
{
Task<string> StoreAsync(Stream content, string fileName);
Task<Stream> RetrieveAsync(string path);
Task DeleteAsync(string path);
}
public class LocalFileStorage : IFileStorage
{
private readonly string _basePath;
public async Task<string> StoreAsync(Stream content, string fileName)
{
var path = Path.Combine(_basePath, Guid.NewGuid().ToString(), fileName);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using var file = File.Create(path);
await content.CopyToAsync(file);
return path; // Return path for database storage
}
}
AI services are external and varied, requiring abstraction.
public interface ICognitiveAdapter
{
int MaxContextTokens { get; }
Task<float[]> CreateEmbeddingAsync(string text, string context);
Task<AssessmentResult> AssessAsync(string content, string criteria, string context);
}
public record AssessmentResult(double Score, string Reasoning);
public class OpenAIAdapter : ICognitiveAdapter
{
public int MaxContextTokens => 128000;
public async Task<float[]> CreateEmbeddingAsync(string text, string context)
{
// Embedding includes journey context
var contextualText = $"{context}\n\n{text}";
return await CallOpenAIEmbeddingAPI(contextualText);
}
public async Task<AssessmentResult> AssessAsync(
string content,
string criteria,
string context)
{
// Assessment within journey's projection space
var prompt = BuildAssessmentPrompt(content, criteria, context);
var response = await CallOpenAICompletionAPI(prompt);
return ParseAssessmentResult(response);
}
}
Services expose methods that map naturally to operations. Database updates are inherently commands. Queries return projections of the normalized model.
public class PersonaService
{
private readonly VeritheiaDbContext _db;
public async Task<Persona> GetActivePersona(Guid userId, string domain)
{
return await _db.Personas
.Where(p => p.UserId == userId)
.Where(p => p.Domain == domain)
.Where(p => p.IsActive)
.FirstOrDefaultAsync();
}
public async Task<Persona> CreatePersona(Guid userId, string domain)
{
var persona = new Persona
{
Id = Guid.CreateVersion7(),
UserId = userId,
Domain = domain,
IsActive = true,
CreatedAt = DateTime.UtcNow
};
_db.Personas.Add(persona);
await _db.SaveChangesAsync();
return persona;
}
}
When performance requires selective denormalization or complex query optimization, encapsulate in query objects. This is CQRS as optimization technique, not architectural pattern.
// Complex search with many parameters
public record DocumentSearchQuery(
Guid UserId,
Guid? JourneyId,
string[] SearchTerms,
DateTime? Since,
DateTime? Until,
int PageNumber,
int PageSize);
public class DocumentSearchHandler
{
private readonly VeritheiaDbContext _db;
public async Task<PagedResult<Document>> HandleAsync(DocumentSearchQuery query)
{
var baseQuery = _db.Documents
.Where(d => d.UserId == query.UserId); // Partition boundary
if (query.JourneyId.HasValue)
{
// Further scope to journey
var documentIds = await _db.JourneyDocumentSegments
.Where(s => s.JourneyId == query.JourneyId.Value)
.Select(s => s.DocumentId)
.Distinct()
.ToListAsync();
baseQuery = baseQuery.Where(d => documentIds.Contains(d.Id));
}
if (query.Since.HasValue)
baseQuery = baseQuery.Where(d => d.UploadedAt >= query.Since.Value);
if (query.Until.HasValue)
baseQuery = baseQuery.Where(d => d.UploadedAt <= query.Until.Value);
// Complex term matching
if (query.SearchTerms?.Any() == true)
{
foreach (var term in query.SearchTerms)
baseQuery = baseQuery.Where(d => d.FileName.Contains(term));
}
var totalCount = await baseQuery.CountAsync();
var documents = await baseQuery
.OrderByDescending(d => d.UploadedAt)
.Skip((query.PageNumber - 1) * query.PageSize)
.Take(query.PageSize)
.ToListAsync();
return new PagedResult<Document>(
documents, totalCount, query.PageNumber, query.PageSize);
}
}
Tests run against real PostgreSQL instances. Respawn resets state between tests.
public class JourneyServiceTests : IAsyncLifetime
{
private VeritheiaDbContext _db;
private Respawner _respawner;
public async Task InitializeAsync()
{
var options = new DbContextOptionsBuilder<VeritheiaDbContext>()
.UseNpgsql("Host=localhost;Database=veritheia_test;Username=test;Password=test")
.Options;
_db = new VeritheiaDbContext(options);
await _db.Database.EnsureCreatedAsync();
_respawner = await Respawner.CreateAsync(
_db.Database.GetConnectionString(),
new RespawnerOptions
{
DbAdapter = DbAdapter.Postgres,
SchemasToInclude = new[] { "public" }
});
}
public async Task DisposeAsync()
{
await _respawner.ResetAsync(_db.Database.GetConnectionString());
await _db.DisposeAsync();
}
[Fact]
public async Task CreateJourney_EnforcesPersonaExists()
{
// Arrange
var service = new JourneyService(_db);
var userId = Guid.CreateVersion7();
var personaId = Guid.CreateVersion7(); // Non-existent
// Act & Assert
// PostgreSQL foreign key constraint prevents this
await Assert.ThrowsAsync<DbUpdateException>(
() => service.CreateJourney(userId, personaId, "Test Purpose"));
}
}
External services (AI, file storage) can be mocked.
public class ScreeningProcessTests
{
[Fact]
public async Task Process_MeasuresWithinJourneyProjection()
{
// Arrange
var mockCognitive = new Mock<ICognitiveAdapter>();
mockCognitive.Setup(c => c.AssessAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(new AssessmentResult(0.8, "Highly relevant"));
var process = new SystematicScreeningProcess(_db, mockCognitive.Object);
// Act
var result = await process.ExecuteAsync(context);
// Assert
mockCognitive.Verify(c => c.AssessAsync(
It.IsAny<string>(),
It.Is<string>(s => s.Contains("research question")),
It.Is<string>(s => s.Contains("journey context"))),
Times.AtLeastOnce());
}
}
All entities use UUIDv7 for temporal ordering without sequence management.
public abstract class BaseEntity
{
public Guid Id { get; set; } = Guid.CreateVersion7();
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
}
// ANTI-PATTERN: Unnecessary abstraction
public interface IRepository<T>
{
Task<T> GetByIdAsync(Guid id);
Task<T> AddAsync(T entity);
}
// DbContext already provides this
// ANTI-PATTERN: Entity with behavior
public class Journey
{
public void Complete()
{
if (State != "Active")
throw new InvalidOperationException();
State = "Completed";
}
}
// PostgreSQL check constraint already enforces valid state transitions
// ANTI-PATTERN: Ignoring partition boundaries
public async Task<List<Document>> SearchDocuments(string term)
{
return await _db.Documents
.Where(d => d.FileName.Contains(term))
.ToListAsync();
}
// Must scope to user partition
// ANTI-PATTERN: Mocking DbContext
var mockDb = new Mock<VeritheiaDbContext>();
// Test against real database with Respawn
When implementing any feature:
These patterns emerge from the recognition that PostgreSQL with pgvector IS our domain model, Entity Framework Core IS our data access layer, and the schema IS the source of truth. We do not abstract what is already abstracted. We do not mock what must be real. We partition by user, project by journey, and measure within conceptual spaces.
Every pattern serves the architectural commitment: intellectual sovereignty through structural design, not policy decoration.