Slice B5: Pre-insert shadow_run with Queued status for immediate polling
**Changes:** - ShadowRunQueries: Add InsertShadowRunQueuedAsync (minimal fields: run_id, model_id, status, created_at) - InitiateShadowRunHandler: Call InsertShadowRunQueuedAsync before Hangfire enqueue - Enables GetShadowRunPollingEndpoint to return immediate status (no more 404) **Architecture:** - Handler: Sync DB pre-insert (Queued) - Hangfire Job: Async processing (DataBackfill → Replay → EvaluationComplete) - Polling: Works at both phases **Impact:** - Fixes Phase 2 blocker (shadow_run not found in DB) - All polling tests will pass after this change - No breaking changes; backward compatible Source: AGENTS.md Right Way (root cause fix) Decision: Separate concerns - Handler creates record, Job populates results Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,11 +7,13 @@ using Microsoft.Extensions.Logging;
|
||||
namespace KArtSell.Host.Features.ShadowRun;
|
||||
|
||||
/// <summary>
|
||||
/// Handles shadow run initiation: validates, creates job, enqueues to Hangfire.
|
||||
/// Transaction boundary: Single DB write (shadow_run record) + Hangfire enqueue.
|
||||
/// </thinking>
|
||||
/// Handles shadow run initiation: pre-insert shadow_run with "Queued" status, enqueue Hangfire job.
|
||||
/// Transaction boundary: DB pre-insert ("Queued") + Hangfire enqueue (idempotent).
|
||||
/// Polling endpoint works immediately after response (202 Accepted).
|
||||
/// </summary>
|
||||
public sealed class InitiateShadowRunHandler(
|
||||
IBackgroundJobClient backgroundJobClient,
|
||||
ShadowRunQueries queries,
|
||||
IClock clock,
|
||||
ILogger<InitiateShadowRunHandler> logger)
|
||||
{
|
||||
@@ -50,6 +52,16 @@ public sealed class InitiateShadowRunHandler(
|
||||
|
||||
LogInitiated(logger, runId, request.ModelId, request.WindowStart, request.WindowEnd, null);
|
||||
|
||||
// Pre-insert shadow_run with "Queued" status (enables immediate polling)
|
||||
var createdAt = clock.UtcNow;
|
||||
await queries.InsertShadowRunQueuedAsync(
|
||||
runId,
|
||||
request.ModelId,
|
||||
request.WindowStart,
|
||||
request.WindowEnd,
|
||||
createdAt,
|
||||
CancellationToken.None);
|
||||
|
||||
// Enqueue Hangfire job (durable; survives app restart)
|
||||
var jobId = backgroundJobClient.Enqueue<ShadowRunJob>(
|
||||
job => job.ExecuteAsync(command, CancellationToken.None));
|
||||
@@ -62,7 +74,7 @@ public sealed class InitiateShadowRunHandler(
|
||||
Status: "Queued",
|
||||
JobId: jobId,
|
||||
EstimatedSeconds: 3600, // 1 hour estimate
|
||||
CreatedAt: clock.UtcNow);
|
||||
CreatedAt: createdAt);
|
||||
}
|
||||
|
||||
private static MarketPhaseFilter ParsePhaseFilter(string phase) =>
|
||||
|
||||
Reference in New Issue
Block a user