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 5.1: Create RunAnomalyDetectorCommand in CLI
Summary
Create the CLI command that triggers anomaly detection runs, enabling manual testing and ad-hoc execution from the command line. The command identifies monitors by name or spec ID (not store GUID).
Dependencies
- TASK 4.1 (IMonitorRunOrchestrator)
- TASK 3.1 (IMonitorSpecRepository)
- TASK 3.3 (MonitorSpecSeeder for auto-creation)
Files to Create
1. backend/Kds.Ai.Cli/Commands/AnomalyDetector/RunAnomalyDetectorCommand.cs
csharp
using ConsoleAppFramework;
using Kds.Ai.AnomalyDetector.Orchestration;
using Kds.Ai.AnomalyDetector.Seeding;
using Kds.Ai.Data.Entities;
using Kds.Ai.Data.Enums;
using Kds.Ai.Data.Repositories;
namespace Kds.Ai.Cli.Commands.AnomalyDetector;
/// <summary>
/// CLI commands for running anomaly detection
/// </summary>
[RegisterCommands("anomaly")]
public class RunAnomalyDetectorCommand(
IMonitorRunOrchestrator orchestrator,
IMonitorSpecRepository specRepository,
MonitorSpecSeeder seeder)
{
/// <summary>
/// Run anomaly detection for a specific monitor spec
/// </summary>
/// <param name="name">-n, Monitor spec name</param>
/// <param name="specId">--spec-id, Monitor spec ID</param>
/// <param name="analysisTime">-t|--time, Analysis time in ISO format</param>
/// <param name="dryRun">-d|--dry-run, Run without saving results or emitting alerts</param>
/// <param name="verbose">-v|--verbose, Show detailed output</param>
[Command("run")]
public async Task Run(
string? name = null,
Guid? specId = null,
string? analysisTime = null,
bool dryRun = false,
bool verbose = false)
{
if (name is null && specId is null)
{
Console.WriteLine("ERROR: Specify either --name (-n) or --spec-id");
return;
}
// Resolve spec
var spec = specId.HasValue
? await specRepository.GetByIdAsync(specId.Value)
: await specRepository.GetByNameAsync(name!);
if (spec is null)
{
Console.WriteLine(specId.HasValue
? $"ERROR: Monitor spec {specId} not found"
: $"ERROR: Monitor spec '{name}' not found");
Console.WriteLine("Use 'anomaly list' to see available monitors or 'anomaly create' to create one.");
return;
}
// Parse analysis time
var time = ParseAnalysisTime(analysisTime);
Console.WriteLine($"Monitor: {spec.Name} ({spec.Id})");
Console.WriteLine($"State: {spec.State}");
Console.WriteLine($"Analysis time: {time:yyyy-MM-dd HH:mm:ss} UTC");
Console.WriteLine();
// Configure options
var options = new RunOptions
{
DryRun = dryRun,
Progress = verbose ? new ConsoleProgress() : null
};
if (dryRun)
{
Console.WriteLine("[DRY RUN MODE - No changes will be saved]");
Console.WriteLine();
}
// Execute
try
{
var run = await orchestrator.ExecuteAsync(spec, time, options);
// Output results
Console.WriteLine();
Console.WriteLine("=== Results ===");
Console.WriteLine($"Run ID: {run.Id}");
Console.WriteLine($"Status: {run.Status}");
Console.WriteLine($"Duration: {run.DurationMs}ms");
Console.WriteLine($"Anomalies Detected: {run.AnomaliesDetected}");
if (run.TokensUsed.HasValue)
{
Console.WriteLine($"Tokens Used: {run.TokensUsed}");
}
if (run.AlertEvents.Count > 0)
{
Console.WriteLine($"Alerts Emitted: {run.AlertEvents.Count}");
foreach (var alert in run.AlertEvents)
{
Console.WriteLine($" - {alert.Id} ({alert.Severity})");
}
}
if (!string.IsNullOrEmpty(run.ErrorMessage))
{
Console.WriteLine($"Error: {run.ErrorMessage}");
}
if (verbose && !string.IsNullOrEmpty(run.ResultSummary))
{
Console.WriteLine();
Console.WriteLine("=== Result Summary ===");
Console.WriteLine(run.ResultSummary);
}
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
if (verbose)
{
Console.WriteLine(ex.ToString());
}
}
}
/// <summary>
/// List monitor specs
/// </summary>
/// <param name="state">--state, Filter by state: draft, active, paused, archived, all</param>
[Command("list")]
public async Task List(string state = "all")
{
IReadOnlyList<MonitorSpec> specs;
if (state.Equals("all", StringComparison.OrdinalIgnoreCase))
{
specs = await specRepository.GetAllAsync();
}
else
{
var stateEnum = Enum.Parse<MonitorState>(state, ignoreCase: true);
specs = await specRepository.GetByStateAsync(stateEnum);
}
if (specs.Count == 0)
{
Console.WriteLine("No monitor specs found.");
return;
}
Console.WriteLine($"Found {specs.Count} monitor specs:");
Console.WriteLine();
foreach (var spec in specs)
{
var lastRun = spec.LastRunAt?.ToString("yyyy-MM-dd HH:mm") ?? "Never";
Console.WriteLine($" ID: {spec.Id}");
Console.WriteLine($" Name: {spec.Name}");
Console.WriteLine($" State: {spec.State}");
Console.WriteLine($" Last Run: {lastRun}");
Console.WriteLine();
}
}
/// <summary>
/// Create a new monitor spec
/// </summary>
/// <param name="name">-n|--name, Monitor name [required]</param>
/// <param name="promptFile">-p|--prompt-file, Path to a text file containing the system prompt</param>
/// <param name="useExample">--example, Use the built-in example prompt</param>
[Command("create")]
public async Task Create(
string name,
string? promptFile = null,
bool useExample = false)
{
var exists = await specRepository.ExistsByNameAsync(name);
if (exists)
{
Console.WriteLine($"ERROR: A monitor with name '{name}' already exists.");
return;
}
string systemPrompt;
if (promptFile is not null)
{
if (!File.Exists(promptFile))
{
Console.WriteLine($"ERROR: Prompt file not found: {promptFile}");
return;
}
systemPrompt = await File.ReadAllTextAsync(promptFile);
}
else if (useExample)
{
systemPrompt = ExamplePrompts.PrepTimeAnomaly;
}
else
{
Console.WriteLine("ERROR: Specify either --prompt-file (-p) or --example");
return;
}
var spec = await seeder.EnsureAsync(name, systemPrompt);
Console.WriteLine("Monitor spec created successfully:");
Console.WriteLine($" ID: {spec.Id}");
Console.WriteLine($" Name: {spec.Name}");
Console.WriteLine($" State: {spec.State}");
Console.WriteLine();
Console.WriteLine("Use 'anomaly set-state --spec-id <id> --state active' to activate it.");
}
/// <summary>
/// Change the state of a monitor spec
/// </summary>
/// <param name="specId">--spec-id, Monitor spec ID [required]</param>
/// <param name="state">--state, New state: draft, active, paused, archived [required]</param>
[Command("set-state")]
public async Task SetState(Guid specId, string state)
{
var spec = await specRepository.GetByIdAsync(specId);
if (spec is null)
{
Console.WriteLine($"ERROR: Monitor spec {specId} not found");
return;
}
var newState = Enum.Parse<MonitorState>(state, ignoreCase: true);
await specRepository.SetStateAsync(specId, newState);
Console.WriteLine($"Monitor '{spec.Name}' state changed to {newState}");
}
private static DateTimeOffset ParseAnalysisTime(string? timeStr)
{
if (string.IsNullOrEmpty(timeStr))
return DateTimeOffset.UtcNow;
// Support relative times
if (timeStr.StartsWith("-"))
{
if (int.TryParse(timeStr[1..^1], out var value))
{
return timeStr[^1] switch
{
'h' => DateTimeOffset.UtcNow.AddHours(-value),
'd' => DateTimeOffset.UtcNow.AddDays(-value),
'm' => DateTimeOffset.UtcNow.AddMinutes(-value),
_ => throw new ArgumentException($"Unknown time unit: {timeStr}")
};
}
}
if (DateTimeOffset.TryParse(timeStr, out var result))
return result;
throw new ArgumentException($"Invalid time format: {timeStr}. Use ISO format (e.g., 2025-01-15T14:00:00Z) or relative (-2h, -1d)");
}
/// <summary>
/// Console progress reporter
/// </summary>
private sealed class ConsoleProgress : IProgress<RunProgress>
{
public void Report(RunProgress value)
{
var prefix = value.Phase switch
{
RunPhase.Completed => "[DONE]",
RunPhase.Failed => "[FAIL]",
_ => "[....]"
};
Console.WriteLine($"{prefix} {value.Phase}: {value.Message}");
}
}
}Command Usage Examples
bash
# Run anomaly detection for a named monitor
dotnet run --project Kds.Ai.Cli -- anomaly run -n "Prep Time Monitor - Downtown"
# Run by spec ID
dotnet run --project Kds.Ai.Cli -- anomaly run --spec-id "abc123-..."
# Dry run with verbose output
dotnet run --project Kds.Ai.Cli -- anomaly run -n "Prep Time Monitor" -d -v
# Run for a specific historical time
dotnet run --project Kds.Ai.Cli -- anomaly run -n "Prep Time Monitor" -t "2025-01-15T14:00:00Z"
# Run with relative time (2 hours ago)
dotnet run --project Kds.Ai.Cli -- anomaly run -n "Prep Time Monitor" -t "-2h"
# List all monitor specs
dotnet run --project Kds.Ai.Cli -- anomaly list
# List only active specs
dotnet run --project Kds.Ai.Cli -- anomaly list --state active
# Create a new monitor with example prompt
dotnet run --project Kds.Ai.Cli -- anomaly create -n "Prep Time Monitor - Downtown" --example
# Create a monitor from a prompt file
dotnet run --project Kds.Ai.Cli -- anomaly create -n "Custom Monitor" -p ./prompts/my-prompt.txt
# Change monitor state to active
dotnet run --project Kds.Ai.Cli -- anomaly set-state --spec-id "abc123-..." --state activeHelp Output
$ dotnet run --project Kds.Ai.Cli -- anomaly --help
anomaly
Commands for anomaly detection
Commands:
run Run anomaly detection for a specific monitor spec
list List monitor specs
create Create a new monitor spec
set-state Change the state of a monitor spec
$ dotnet run --project Kds.Ai.Cli -- anomaly run --help
anomaly run
Run anomaly detection for a specific monitor spec
Options:
-n, --name Monitor spec name
--spec-id Monitor spec ID
-t, --time Analysis time in ISO format or relative (-2h, -1d)
-d, --dry-run Run without saving results or emitting alerts
-v, --verbose Show detailed outputChanges from Old Design
runcommand takes--nameor--spec-idinstead of--store(MonitorSpec has no StoreGuid)- No
--createflag onrun- useanomaly createinstead listfilters by--state(MonitorState enum) instead of--store(StoreGuid)createtakes--prompt-fileor--exampleinstead of--store+ auto-creating fromPrepTimeMonitorSpecFactory- New
set-statecommand replacestoggle --enable/--disable(MonitorState has 4 states, not boolean) - Uses
run.AlertEventscollection instead ofrun.AlertEventId - No references to
PrepTimeMonitorSpecFactory,StoreGuid,MonitorType,IsEnabled
Acceptance Criteria
- [ ]
RunAnomalyDetectorCommandclass created - [ ]
[RegisterCommands("anomaly")]attribute applied - [ ]
runcommand accepts--nameor--spec-id(not--store) - [ ]
listcommand filters by--state(not--store) - [ ]
createcommand accepts--prompt-fileor--example - [ ]
set-statecommand transitions MonitorState - [ ] Dry-run mode prevents persistence
- [ ] Verbose mode shows progress
- [ ] Relative time parsing (
-2h,-1d) works - [ ] Uses
run.AlertEvents(collection, not single FK) - [ ] No references to
StoreGuid,MonitorType,IsEnabled,PrepTimeMonitorSpecFactory - [ ] Help text displays correctly
- [ ] Solution builds without errors
Notes
- ConsoleAppFramework handles argument parsing and help generation
- Parameter names automatically become options (e.g.,
namebecomes--name) - Aliases are defined in XML doc comments (e.g.,
-n|--namein<param>tag) - No
[Option]attributes needed - they are deprecated in ConsoleAppFramework v5 - Progress reporting only shown in verbose mode to reduce noise
- Monitors are created in
Draftstate by default, must be activated withset-state