Built Word Documents (.docx):
Spensara Overview (.docx)
Capability Assessment (.docx)
Brink POS Ingestion Analysis (.docx)
The Borg Ingestion Architecture (.docx)
Categories
Implementation Specs (157)
Detailed engineering and feature implementation specifications.
- specs/AnomalyDetection/TASK1.1.md
- specs/AnomalyDetection/TASK1.2.md
- specs/AnomalyDetection/TASK1.3.md
- specs/AnomalyDetection/TASK1.4.md
- specs/AnomalyDetection/TASK2.1.md
- specs/AnomalyDetection/TASK2.2.md
- specs/AnomalyDetection/TASK3.1.md
- specs/AnomalyDetection/TASK3.2.md
- specs/AnomalyDetection/TASK3.3.md
- specs/AnomalyDetection/TASK4.1.md
- specs/AnomalyDetection/TASK4.2.md
- specs/AnomalyDetection/TASK4.3.md
- specs/AnomalyDetection/TASK4.4.md
- specs/AnomalyDetection/TASK5.1.md
- specs/AnomalyDetection/TASK5.2.md
- specs/AnomalyDetection/TASK5.3.md
- specs/AnomalyDetection/TASK6.1.md
- specs/AnomalyDetection/TASK6.2.md
- specs/AnomalyDetection/TASK6.3.md
- specs/AnomalyDetection/TASKS.md
- specs/ApiServicePostgresTestStability/TASK.md
- specs/BackendDatabaseConsolidation/TASK.md
- specs/BackendDatabaseConsolidation/TASK1.1.md
- specs/BackendDatabaseConsolidation/TASK1.2.md
- specs/BackendDatabaseConsolidation/TASK1.3.md
- specs/BackendDatabaseConsolidation/TASK2.1.md
- specs/BackendDatabaseConsolidation/TASK2.2.md
- specs/BackendDatabaseConsolidation/TASK2.3.md
- specs/BackendDatabaseConsolidation/TASK3.1.md
- specs/BackendDatabaseConsolidation/TASK3.2.md
- specs/BackendDatabaseConsolidation/TASK4.1.md
- specs/BackendDatabaseConsolidation/TASK4.2.md
- specs/BackendDatabaseConsolidation/TASK4.3.md
- specs/BackendDatabaseConsolidation/TASK5.1.md
- specs/BackendDatabaseConsolidation/TASK5.2.md
- specs/BrinkLaborGetShiftsStaging/TASK.md
- specs/BrinkPosOrdersFirstEtlJob/TASK.md
- specs/BrinkPosOrdersFirstEtlJob/TASK0.md
- specs/BrinkPosOrdersFirstEtlJob/TASK1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK5.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.1.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.2.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.3.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.4.md
- specs/BrinkPosOrdersFirstEtlJob/TASK6.md
- specs/BrinkPosOrdersFirstEtlJob/TASK7.md
- specs/BrinkPosOrdersFirstEtlJob/TASK8.md
- specs/BrinkPosOrdersFirstEtlJob/TASK9.md
- specs/BrinkSettings2ToStaging/TASK.MD
- specs/CuratedToS3Stage/TASK.md
- specs/CuratedToS3Stage/TASK0.md
- specs/CuratedToS3Stage/TASK1.md
- specs/CuratedToS3Stage/TASK2.md
- specs/CuratedToS3Stage/TASK3.md
- specs/CuratedToS3Stage/TASK4.md
- specs/CustomerCredentialCustomHeaders/TASK.md
- specs/CustomerCredentialCustomHeaders/TASK1.1.md
- specs/CustomerCredentialCustomHeaders/TASK1.2.md
- specs/CustomerCredentialCustomHeaders/TASK2.1.md
- specs/CustomerCredentialCustomHeaders/TASK2.2.md
- specs/CustomerCredentialCustomHeaders/TASK3.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK.md
- specs/CustomerCredentialTriggeredProvisioning/TASK1.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK1.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK2.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK2.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK3.1.md
- specs/CustomerCredentialTriggeredProvisioning/TASK3.2.md
- specs/CustomerCredentialTriggeredProvisioning/TASK4.1.md
- specs/EtlSourceAdapterFramework/TASK.md
- specs/EtlSourceAdapterFramework/TASK0.md
- specs/EtlSourceAdapterFramework/TASK1.md
- specs/EtlSourceAdapterFramework/TASK2.md
- specs/EtlSourceAdapterFramework/TASK3.md
- specs/EtlSourceAdapterFramework/TASK4.md
- specs/EtlSourceAdapterFramework/TASK5.md
- specs/EtlSourceAdapterFramework/TASK6.md
- specs/KdsCdcDebeziumIntegration/TASK.md
- specs/KdsCdcDebeziumIntegration/TASK1.md
- specs/KdsCdcDebeziumIntegration/TASK2.md
- specs/KdsCdcDebeziumIntegration/TASK3.md
- specs/KdsCdcDebeziumIntegration/TASK4.md
- specs/KdsCdcDebeziumIntegration/TASK5.md
- specs/KdsCdcDurableRelationRouting/TASK.md
- specs/KdsCdcDurableRelationRouting/TASK1.md
- specs/KdsCdcDurableRelationRouting/TASK2.md
- specs/KdsCdcDurableRelationRouting/TASK3.md
- specs/KdsCdcDurableRelationRouting/TASK4.md
- specs/KdsCdcDurableRelationRouting/TASK5.md
- specs/KdsCdcIngestion/TASK.md
- specs/KdsCdcIngestion/TASK1.md
- specs/KdsCdcIngestion/TASK2.md
- specs/KdsCdcIngestion/TASK3.md
- specs/KdsCdcIngestion/TASK4.md
- specs/KdsCdcIngestion/TASK5.md
- specs/KdsCdcIngestion/TASK6.md
- specs/KdsCdcIngestion/TASK7.md
- specs/KdsCdcIngestion/TASK8.md
- specs/KdsCdcIngestion/TASK9.md
- specs/KdsCdcReplayExecution/TASK.md
- specs/KdsCdcReplayExecution/TASK1.md
- specs/KdsCdcReplayExecution/TASK2.md
- specs/KdsCdcReplayExecution/TASK3.md
- specs/KdsCdcReplayExecution/TASK4.md
- specs/KdsMonitorServingContractMigration/TASK.md
- specs/KdsMonitorServingContractMigration/TASK1.1.md
- specs/KdsMonitorServingContractMigration/TASK1.2.md
- specs/KdsMonitorServingContractMigration/TASK2.1.md
- specs/KdsMonitorServingContractMigration/TASK2.2.md
- specs/KdsMonitorServingContractMigration/TASK3.1.md
- specs/KdsMonitorServingContractMigration/TASK3.2.md
- specs/RedshiftFoundation/TASK.md
- specs/RedshiftFoundation/TASK1.1.md
- specs/RedshiftFoundation/TASK1.2.md
- specs/RedshiftFoundation/TASK1.3.md
- specs/RedshiftFoundation/TASK1.4.md
- specs/RedshiftFoundation/TASK2.1.md
- specs/RedshiftFoundation/TASK2.2.md
- specs/RedshiftFoundation/TASK3.1.md
- specs/RedshiftFoundation/TASK3.2.md
- specs/RedshiftFoundation/TASK3.3.md
- specs/RedshiftSyntheticDataFeed/TASK.md
- specs/RedshiftSyntheticDataFeed/TASK1.1.md
- specs/RedshiftSyntheticDataFeed/TASK1.2.md
- specs/RedshiftSyntheticDataFeed/TASK1.3.md
- specs/RedshiftSyntheticDataFeed/TASK2.1.md
- specs/RedshiftSyntheticDataFeed/TASK2.2.md
- specs/RedshiftSyntheticDataFeed/TASK3.1.md
- specs/RedshiftSyntheticDataFeed/TASK3.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK1.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK1.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK2.3.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK3.1.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK3.2.md
- specs/SingleStoreRedshiftRuntimeReadPath/TASK4.1.md
TASK 4.4: Add MonitorRun History Recording
Summary
Create the repository interface and implementation for managing MonitorRun entities, enabling tracking and querying of monitor execution history.
Dependencies
- TASK 1.2 (MonitorRun entity)
- TASK 1.3 (Migration applied)
Files to Create
1. backend/Kds.Ai.AnomalyDetector/Repositories/IMonitorRunRepository.cs
csharp
using Kds.Ai.Data.Entities;
using Kds.Ai.Data.Enums;
namespace Kds.Ai.AnomalyDetector.Repositories;
/// <summary>
/// Repository for managing MonitorRun execution history
/// </summary>
public interface IMonitorRunRepository
{
/// <summary>
/// Get a run by ID
/// </summary>
Task<MonitorRun?> GetByIdAsync(Guid id, CancellationToken ct = default);
/// <summary>
/// Get a run by ID with related MonitorSpec loaded
/// </summary>
Task<MonitorRun?> GetByIdWithSpecAsync(Guid id, CancellationToken ct = default);
/// <summary>
/// Get all runs for a monitor spec
/// </summary>
Task<IReadOnlyList<MonitorRun>> GetByMonitorSpecAsync(
Guid monitorSpecId,
int limit = 100,
CancellationToken ct = default);
/// <summary>
/// Get the most recent run for a monitor spec
/// </summary>
Task<MonitorRun?> GetLatestByMonitorSpecAsync(
Guid monitorSpecId,
CancellationToken ct = default);
/// <summary>
/// Get runs within a time range
/// </summary>
Task<IReadOnlyList<MonitorRun>> GetByTimeRangeAsync(
DateTimeOffset from,
DateTimeOffset to,
CancellationToken ct = default);
/// <summary>
/// Get runs by status
/// </summary>
Task<IReadOnlyList<MonitorRun>> GetByStatusAsync(
MonitorRunStatus status,
int limit = 100,
CancellationToken ct = default);
/// <summary>
/// Get runs that detected anomalies
/// </summary>
Task<IReadOnlyList<MonitorRun>> GetWithAnomaliesAsync(
int limit = 100,
CancellationToken ct = default);
/// <summary>
/// Create a new run record
/// </summary>
Task<MonitorRun> CreateAsync(MonitorRun run, CancellationToken ct = default);
/// <summary>
/// Update a run record
/// </summary>
Task<MonitorRun> UpdateAsync(MonitorRun run, CancellationToken ct = default);
/// <summary>
/// Mark a run as completed with results
/// </summary>
Task MarkCompletedAsync(
Guid id,
int anomaliesDetected,
string? resultSummary,
int? tokensUsed,
CancellationToken ct = default);
/// <summary>
/// Mark a run as failed
/// </summary>
Task MarkFailedAsync(
Guid id,
string errorMessage,
CancellationToken ct = default);
/// <summary>
/// Get statistics for runs
/// </summary>
Task<RunStatistics> GetStatisticsAsync(
DateTimeOffset? from = null,
DateTimeOffset? to = null,
CancellationToken ct = default);
/// <summary>
/// Delete old run records for cleanup
/// </summary>
/// <param name="olderThan">Delete runs older than this date</param>
/// <param name="keepMinimum">Keep at least this many runs per spec</param>
/// <param name="ct">Cancellation token</param>
/// <returns>Number of runs deleted</returns>
Task<int> CleanupOldRunsAsync(
DateTimeOffset olderThan,
int keepMinimum = 10,
CancellationToken ct = default);
}
/// <summary>
/// Statistics about monitor runs
/// </summary>
public sealed record RunStatistics
{
public required int TotalRuns { get; init; }
public required int CompletedRuns { get; init; }
public required int FailedRuns { get; init; }
public required int RunsWithAnomalies { get; init; }
public required int TotalAnomaliesDetected { get; init; }
public required double AverageRunDurationMs { get; init; }
public required int TotalTokensUsed { get; init; }
}2. backend/Kds.Ai.AnomalyDetector/Repositories/EfMonitorRunRepository.cs
csharp
using Kds.Ai.Data;
using Kds.Ai.Data.Entities;
using Kds.Ai.Data.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Kds.Ai.AnomalyDetector.Repositories;
internal sealed class EfMonitorRunRepository(
KdsAiDbContext dbContext,
ILogger<EfMonitorRunRepository> logger) : IMonitorRunRepository
{
public async Task<MonitorRun?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.FirstOrDefaultAsync(x => x.Id == id, ct);
}
public async Task<MonitorRun?> GetByIdWithSpecAsync(Guid id, CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Include(x => x.MonitorSpec)
.FirstOrDefaultAsync(x => x.Id == id, ct);
}
public async Task<IReadOnlyList<MonitorRun>> GetByMonitorSpecAsync(
Guid monitorSpecId,
int limit = 100,
CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Where(x => x.MonitorSpecId == monitorSpecId)
.OrderByDescending(x => x.StartedAt)
.Take(limit)
.ToListAsync(ct);
}
public async Task<MonitorRun?> GetLatestByMonitorSpecAsync(
Guid monitorSpecId,
CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Where(x => x.MonitorSpecId == monitorSpecId)
.OrderByDescending(x => x.StartedAt)
.FirstOrDefaultAsync(ct);
}
public async Task<IReadOnlyList<MonitorRun>> GetByTimeRangeAsync(
DateTimeOffset from,
DateTimeOffset to,
CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Where(x => x.StartedAt >= from && x.StartedAt < to)
.OrderByDescending(x => x.StartedAt)
.ToListAsync(ct);
}
public async Task<IReadOnlyList<MonitorRun>> GetByStatusAsync(
MonitorRunStatus status,
int limit = 100,
CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Where(x => x.Status == status)
.OrderByDescending(x => x.StartedAt)
.Take(limit)
.ToListAsync(ct);
}
public async Task<IReadOnlyList<MonitorRun>> GetWithAnomaliesAsync(
int limit = 100,
CancellationToken ct = default)
{
return await dbContext.MonitorRuns
.Where(x => x.AnomaliesDetected > 0)
.OrderByDescending(x => x.StartedAt)
.Take(limit)
.ToListAsync(ct);
}
public async Task<MonitorRun> CreateAsync(MonitorRun run, CancellationToken ct = default)
{
if (run.Id == Guid.Empty)
{
run.Id = Guid.NewGuid();
}
dbContext.MonitorRuns.Add(run);
await dbContext.SaveChangesAsync(ct);
logger.LogDebug("Created MonitorRun {Id} for spec {SpecId}", run.Id, run.MonitorSpecId);
return run;
}
public async Task<MonitorRun> UpdateAsync(MonitorRun run, CancellationToken ct = default)
{
dbContext.MonitorRuns.Update(run);
await dbContext.SaveChangesAsync(ct);
return run;
}
public async Task MarkCompletedAsync(
Guid id,
int anomaliesDetected,
string? resultSummary,
int? tokensUsed,
CancellationToken ct = default)
{
var now = DateTimeOffset.UtcNow;
await dbContext.MonitorRuns
.Where(x => x.Id == id)
.ExecuteUpdateAsync(setters => setters
.SetProperty(x => x.Status, MonitorRunStatus.Completed)
.SetProperty(x => x.CompletedAt, now)
.SetProperty(x => x.AnomaliesDetected, anomaliesDetected)
.SetProperty(x => x.ResultSummary, resultSummary)
.SetProperty(x => x.TokensUsed, tokensUsed), ct);
}
public async Task MarkFailedAsync(
Guid id,
string errorMessage,
CancellationToken ct = default)
{
await dbContext.MonitorRuns
.Where(x => x.Id == id)
.ExecuteUpdateAsync(setters => setters
.SetProperty(x => x.Status, MonitorRunStatus.Failed)
.SetProperty(x => x.CompletedAt, DateTimeOffset.UtcNow)
.SetProperty(x => x.ErrorMessage, errorMessage), ct);
}
public async Task<RunStatistics> GetStatisticsAsync(
DateTimeOffset? from = null,
DateTimeOffset? to = null,
CancellationToken ct = default)
{
var query = dbContext.MonitorRuns.AsQueryable();
if (from.HasValue)
query = query.Where(x => x.StartedAt >= from.Value);
if (to.HasValue)
query = query.Where(x => x.StartedAt < to.Value);
var stats = await query
.GroupBy(x => 1)
.Select(g => new
{
TotalRuns = g.Count(),
CompletedRuns = g.Count(x => x.Status == MonitorRunStatus.Completed),
FailedRuns = g.Count(x => x.Status == MonitorRunStatus.Failed),
RunsWithAnomalies = g.Count(x => x.AnomaliesDetected > 0),
TotalAnomalies = g.Sum(x => x.AnomaliesDetected),
AvgDuration = g.Average(x => (double?)x.DurationMs) ?? 0,
TotalTokens = g.Sum(x => x.TokensUsed ?? 0)
})
.FirstOrDefaultAsync(ct);
return new RunStatistics
{
TotalRuns = stats?.TotalRuns ?? 0,
CompletedRuns = stats?.CompletedRuns ?? 0,
FailedRuns = stats?.FailedRuns ?? 0,
RunsWithAnomalies = stats?.RunsWithAnomalies ?? 0,
TotalAnomaliesDetected = stats?.TotalAnomalies ?? 0,
AverageRunDurationMs = stats?.AvgDuration ?? 0,
TotalTokensUsed = stats?.TotalTokens ?? 0
};
}
public async Task<int> CleanupOldRunsAsync(
DateTimeOffset olderThan,
int keepMinimum = 10,
CancellationToken ct = default)
{
// Get specs with more than minimum runs
var specsToClean = await dbContext.MonitorRuns
.GroupBy(x => x.MonitorSpecId)
.Where(g => g.Count() > keepMinimum)
.Select(g => g.Key)
.ToListAsync(ct);
var totalDeleted = 0;
foreach (var specId in specsToClean)
{
// Get IDs to keep (most recent)
var keepIds = await dbContext.MonitorRuns
.Where(x => x.MonitorSpecId == specId)
.OrderByDescending(x => x.StartedAt)
.Take(keepMinimum)
.Select(x => x.Id)
.ToListAsync(ct);
// Delete old runs
var deleted = await dbContext.MonitorRuns
.Where(x => x.MonitorSpecId == specId)
.Where(x => x.StartedAt < olderThan)
.Where(x => !keepIds.Contains(x.Id))
.ExecuteDeleteAsync(ct);
totalDeleted += deleted;
}
logger.LogInformation("Cleaned up {Count} old monitor runs", totalDeleted);
return totalDeleted;
}
}Files to Modify
3. backend/Kds.Ai.AnomalyDetector/DependencyInjectionExtensions.cs
Add repository registration:
csharp
// Register run repository
builder.Services.AddScoped<IMonitorRunRepository, EfMonitorRunRepository>();Changes from Old Design
- Removed
GetByStoreAsync(storeGuid)- MonitorSpec no longer hasStoreGuid MarkCompletedAsyncno longer takesGuid? alertEventIdparameter - alerts are tracked viaMonitorRun.AlertEventscollection (many-to-many join table), not a single FK- Class is
internal sealedwith primary constructor RunStatisticsis asealed record
Query Patterns
| Use Case | Method |
|---|---|
| View run history | GetByMonitorSpecAsync |
| Check last run | GetLatestByMonitorSpecAsync |
| Find failures | GetByStatusAsync(Failed) |
| Dashboard | GetStatisticsAsync |
| Cleanup job | CleanupOldRunsAsync |
Acceptance Criteria
- [ ]
IMonitorRunRepositoryinterface created - [ ]
EfMonitorRunRepositoryimplementation created (internal sealedwith primary constructor) - [ ] All CRUD methods implemented
- [ ] No
GetByStoreAsyncmethod (removed) - [ ]
MarkCompletedAsyncdoes NOT takealertEventIdparameter - [ ] Query methods with proper ordering (newest first)
- [ ]
GetStatisticsAsyncreturns aggregated metrics - [ ]
CleanupOldRunsAsyncpreserves minimum runs per spec - [ ] Partial update methods use
ExecuteUpdateAsync - [ ] No references to
StoreGuidorAlertEventId(single FK) - [ ] Repository registered in DI
- [ ] Solution builds without errors
Notes
- Consider adding pagination for large result sets
- Statistics query may be slow on large tables - consider caching
- Cleanup should run periodically (e.g., daily via worker)
- May want to add
GetPendingRunsAsyncfor orphan detection