feat(ai): enforce local-first routing
Keep external providers behind server consent, task, and prompt-cost gates while persisting actual provider provenance.
This commit is contained in:
@@ -159,10 +159,23 @@ public sealed class UserOperationStore(JobTrackerContext db, TimeProvider timePr
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> CompleteAsync(Guid operationId, string leaseToken, string? resultReference, CancellationToken cancellationToken)
|
||||
public Task<int> CompleteAsync(Guid operationId, string leaseToken, string? resultReference, CancellationToken cancellationToken)
|
||||
=> CompleteAsync(operationId, leaseToken, resultReference, null, null, null, cancellationToken);
|
||||
|
||||
public async Task<int> CompleteAsync(
|
||||
Guid operationId,
|
||||
string leaseToken,
|
||||
string? resultReference,
|
||||
string? provider,
|
||||
string? model,
|
||||
string? completionStage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
EnsureOwnerScope();
|
||||
ValidateOptional(resultReference, 256, nameof(resultReference));
|
||||
ValidateOptional(provider, 128, nameof(provider));
|
||||
ValidateOptional(model, 128, nameof(model));
|
||||
ValidateOptional(completionStage, 64, nameof(completionStage));
|
||||
var now = UtcNow;
|
||||
await using var transaction = await BeginTransactionAsync(cancellationToken);
|
||||
var operation = await db.UserOperations.AsNoTracking().FirstOrDefaultAsync(item => item.Id == operationId, cancellationToken);
|
||||
@@ -173,6 +186,9 @@ public sealed class UserOperationStore(JobTrackerContext db, TimeProvider timePr
|
||||
.ExecuteUpdateAsync(setters => setters
|
||||
.SetProperty(operation => operation.Status, OperationStatuses.Succeeded)
|
||||
.SetProperty(operation => operation.ResultReference, resultReference)
|
||||
.SetProperty(operation => operation.Provider, provider)
|
||||
.SetProperty(operation => operation.Model, model)
|
||||
.SetProperty(operation => operation.ProgressStage, completionStage)
|
||||
.SetProperty(operation => operation.CompletedAtUtc, now)
|
||||
.SetProperty(operation => operation.ProgressPercent, 100)
|
||||
.SetProperty(operation => operation.LeaseToken, (string?)null)
|
||||
@@ -187,11 +203,27 @@ public sealed class UserOperationStore(JobTrackerContext db, TimeProvider timePr
|
||||
return affected;
|
||||
}
|
||||
|
||||
public async Task<bool> FailAsync(Guid operationId, string leaseToken, bool retryable, string category, string message, TimeSpan retryDelay, CancellationToken cancellationToken)
|
||||
public Task<bool> FailAsync(Guid operationId, string leaseToken, bool retryable, string category, string message, TimeSpan retryDelay, CancellationToken cancellationToken)
|
||||
=> FailAsync(operationId, leaseToken, retryable, category, message, retryDelay, null, null, null, cancellationToken);
|
||||
|
||||
public async Task<bool> FailAsync(
|
||||
Guid operationId,
|
||||
string leaseToken,
|
||||
bool retryable,
|
||||
string category,
|
||||
string message,
|
||||
TimeSpan retryDelay,
|
||||
string? provider,
|
||||
string? model,
|
||||
string? progressStage,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
EnsureOwnerScope();
|
||||
ValidateRequired(category, 64, nameof(category));
|
||||
ValidateRequired(message, 512, nameof(message));
|
||||
ValidateOptional(provider, 128, nameof(provider));
|
||||
ValidateOptional(model, 128, nameof(model));
|
||||
ValidateOptional(progressStage, 64, nameof(progressStage));
|
||||
if (retryDelay < TimeSpan.Zero || retryDelay > TimeSpan.FromHours(1)) throw new ArgumentOutOfRangeException(nameof(retryDelay));
|
||||
await using var transaction = await BeginTransactionAsync(cancellationToken);
|
||||
var operation = await db.UserOperations.AsNoTracking()
|
||||
@@ -209,6 +241,9 @@ public sealed class UserOperationStore(JobTrackerContext db, TimeProvider timePr
|
||||
.SetProperty(item => item.CompletedAtUtc, canRetry ? null : now)
|
||||
.SetProperty(item => item.FailureCategory, category)
|
||||
.SetProperty(item => item.FailureMessage, message)
|
||||
.SetProperty(item => item.Provider, provider)
|
||||
.SetProperty(item => item.Model, model)
|
||||
.SetProperty(item => item.ProgressStage, progressStage)
|
||||
.SetProperty(item => item.LeaseToken, (string?)null)
|
||||
.SetProperty(item => item.LeaseExpiresAtUtc, (DateTime?)null),
|
||||
cancellationToken);
|
||||
@@ -290,6 +325,8 @@ public sealed class UserOperationStore(JobTrackerContext db, TimeProvider timePr
|
||||
operation.FailureCategory = null;
|
||||
operation.FailureMessage = null;
|
||||
operation.ResultReference = null;
|
||||
operation.Provider = null;
|
||||
operation.Model = null;
|
||||
operation.ProgressStage = null;
|
||||
operation.ProgressPercent = null;
|
||||
var existingNotification = await db.UserNotifications.FirstOrDefaultAsync(item => item.OperationId == operationId, cancellationToken);
|
||||
|
||||
Reference in New Issue
Block a user