Initial commit: Add project files
ci / backend (push) Failing after 12s
ci / frontend (push) Failing after 19s
ci / static (push) Failing after 45s

This commit is contained in:
2026-08-02 05:15:36 +09:00
commit dcd1322d41
636 changed files with 122352 additions and 0 deletions
@@ -0,0 +1,68 @@
using Dapper;
using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Versioning;
using KArtSell.Modules.ModelOperations.Application;
namespace KArtSell.Modules.ModelOperations.Infrastructure;
public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connectionFactory) : IApprovedModelContextReader
{
private const string Sql = """
select mv.scope_key as ScopeKey,
mv.model_version as ModelVersion,
mv.config_version as ConfigVersion,
mv.code_sha as CodeSha,
mv.contract_version as ContractVersion,
mv.lifecycle_state as LifecycleState,
mv.effective_at as EffectiveAt,
dm.dataset_id as DatasetId,
dm.content_hash as DataHash
from governance.model_version_registry mv
join lateral (
select dataset_id, content_hash
from evaluation.dataset_manifest
where scope_key = mv.scope_key
and status = 'APPROVED'
and frozen_at <= @AsOf
order by frozen_at desc
limit 1
) dm on true
where mv.scope_key = @ScopeKey
and mv.lifecycle_state in ('RESEARCH', 'CHALLENGER', 'SHADOW', 'CANDIDATE', 'APPROVED')
and mv.effective_at <= @AsOf
order by mv.effective_at desc
limit 1;
""";
public async Task<ApprovedModelContext?> ReadAsync(
string scopeKey,
DateTimeOffset asOf,
CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
var row = await connection.QuerySingleOrDefaultAsync<Row>(new CommandDefinition(
Sql,
new { ScopeKey = scopeKey, AsOf = asOf },
cancellationToken: cancellationToken));
if (row is null)
return null;
return new ApprovedModelContext(
row.ScopeKey,
new VersionSet(row.DatasetId, row.DataHash, row.ModelVersion, row.ConfigVersion, row.CodeSha, row.ContractVersion),
row.LifecycleState,
row.EffectiveAt);
}
private sealed record Row(
string ScopeKey,
string DatasetId,
string DataHash,
string ModelVersion,
string ConfigVersion,
string CodeSha,
string ContractVersion,
string LifecycleState,
DateTimeOffset EffectiveAt);
}
@@ -0,0 +1,103 @@
using System.Text.Json;
using Dapper;
using KArtSell.BuildingBlocks.Data;
using KArtSell.BuildingBlocks.Hashing;
using KArtSell.BuildingBlocks.Reliability;
using KArtSell.Modules.ModelOperations.Application;
namespace KArtSell.Modules.ModelOperations.Infrastructure;
public sealed class DapperModelOperationRequestRepository(
IDbConnectionFactory connectionFactory,
IOutboxWriter outboxWriter) : IModelOperationRequestRepository
{
private const string InsertSql = """
insert into evaluation.model_operation_request
(request_id, schedule_id, operation_code, scope_key, automation_mode, idempotency_key,
dataset_id, data_hash, model_version, config_version, code_sha, contract_version,
lifecycle_state, correlation_id, status, requested_at)
values
(@RequestId, @ScheduleId, @OperationCode, @ScopeKey, @AutomationMode, @IdempotencyKey,
@DatasetId, @DataHash, @ModelVersion, @ConfigVersion, @CodeSha, @ContractVersion,
@LifecycleState, @CorrelationId, 'REQUESTED', @RequestedAt)
on conflict (idempotency_key) do nothing;
""";
private const string InsertStatusEventSql = """
insert into evaluation.model_operation_status_event
(event_id, request_id, from_status, to_status, reason_code, payload_hash,
actor_type, correlation_id, occurred_at)
values
(@EventId, @RequestId, null, 'REQUESTED', null, @PayloadHash,
'SYSTEM', @CorrelationId, @OccurredAt);
""";
public async Task<bool> AddAsync(ModelOperationRequest request, CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
var affected = await connection.ExecuteAsync(new CommandDefinition(
InsertSql,
new
{
request.RequestId,
request.ScheduleId,
request.OperationCode,
request.ScopeKey,
request.AutomationMode,
request.IdempotencyKey,
request.Context.VersionSet.DatasetId,
request.Context.VersionSet.DataHash,
request.Context.VersionSet.ModelVersion,
request.Context.VersionSet.ConfigVersion,
request.Context.VersionSet.CodeSha,
request.Context.VersionSet.ContractVersion,
request.Context.LifecycleState,
request.CorrelationId,
request.RequestedAt
},
transaction,
cancellationToken: cancellationToken));
if (affected == 1)
{
var payload = JsonSerializer.Serialize(new
{
requestId = request.RequestId,
request.OperationCode,
request.ScopeKey,
request.AutomationMode,
versionSet = request.Context.VersionSet,
boundary = "NO_AUTO_MODEL_MUTATION"
});
var payloadHash = ContentHasher.Sha256(payload);
await connection.ExecuteAsync(new CommandDefinition(
InsertStatusEventSql,
new
{
EventId = Guid.NewGuid(),
request.RequestId,
PayloadHash = payloadHash,
request.CorrelationId,
OccurredAt = request.RequestedAt
},
transaction,
cancellationToken: cancellationToken));
var message = new OutboxMessage(
request.RequestId,
"ModelOperationRequested",
1,
payload,
request.CorrelationId,
request.RequestedAt,
payloadHash);
await outboxWriter.AddAsync(connection, transaction, message, cancellationToken);
}
await transaction.CommitAsync(cancellationToken);
return affected == 1;
}
}
@@ -0,0 +1,81 @@
using Dapper;
using KArtSell.BuildingBlocks.Data;
using KArtSell.Modules.ModelOperations.Application;
namespace KArtSell.Modules.ModelOperations.Infrastructure;
public sealed class DapperModelScheduleRepository(IDbConnectionFactory connectionFactory) : IModelScheduleRepository
{
private const string AcquireSql = """
with due as (
select schedule_id
from evaluation.model_operation_schedule
where enabled = true
and next_due_at <= @Now
and (lease_until is null or lease_until < @Now)
order by next_due_at, operation_code, scope_key
for update skip locked
limit @Limit
)
update evaluation.model_operation_schedule s
set lease_owner = @LeaseOwner,
lease_until = @LeaseUntil,
dispatch_revision = dispatch_revision + 1,
updated_at = @Now
from due
where s.schedule_id = due.schedule_id
returning s.schedule_id as ScheduleId,
s.operation_code as OperationCode,
s.scope_key as ScopeKey,
s.cadence as Cadence,
s.automation_mode as AutomationMode,
s.queue_name as Queue,
concat(s.operation_code, ':', s.scope_key, ':', s.schedule_version, ':', to_char(s.next_due_at at time zone 'UTC', 'YYYYMMDDHH24MISS')) as IdempotencyKey,
s.schedule_version as ScheduleVersion,
s.next_due_at as ScheduledFor,
s.catch_up_policy as CatchUpPolicy,
s.max_catch_up as MaxCatchUp;
""";
private const string DispatchedSql = """
update evaluation.model_operation_schedule
set last_dispatched_at = @DispatchedAt,
last_background_job_id = @BackgroundJobId,
next_due_at = @NextDueAt,
lease_owner = null,
lease_until = null,
last_error_code = null,
updated_at = @DispatchedAt
where schedule_id = @ScheduleId and lease_owner = @LeaseOwner;
""";
private const string ReleaseSql = """
update evaluation.model_operation_schedule
set lease_owner = null,
lease_until = null,
last_error_code = @ReasonCode,
next_due_at = greatest(next_due_at, @ReleasedAt) + interval '1 hour',
updated_at = @ReleasedAt
where schedule_id = @ScheduleId and lease_owner = @LeaseOwner;
""";
public async Task<IReadOnlyList<DueModelOperation>> AcquireDueAsync(DateTimeOffset now, string leaseOwner, TimeSpan leaseDuration, int limit, CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
var items = await connection.QueryAsync<DueModelOperation>(new CommandDefinition(AcquireSql, new { Now = now, LeaseOwner = leaseOwner, LeaseUntil = now.Add(leaseDuration), Limit = limit }, cancellationToken: cancellationToken));
return items.AsList();
}
public async Task MarkDispatchedAsync(Guid scheduleId, string leaseOwner, string backgroundJobId, DateTimeOffset dispatchedAt, DateTimeOffset nextDueAt, CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
var affected = await connection.ExecuteAsync(new CommandDefinition(DispatchedSql, new { ScheduleId = scheduleId, LeaseOwner = leaseOwner, BackgroundJobId = backgroundJobId, DispatchedAt = dispatchedAt, NextDueAt = nextDueAt }, cancellationToken: cancellationToken));
if (affected != 1) throw new InvalidOperationException("Schedule lease was lost before dispatch completion.");
}
public async Task ReleaseAsync(Guid scheduleId, string leaseOwner, string reasonCode, DateTimeOffset releasedAt, CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
await connection.ExecuteAsync(new CommandDefinition(ReleaseSql, new { ScheduleId = scheduleId, LeaseOwner = leaseOwner, ReasonCode = reasonCode, ReleasedAt = releasedAt }, cancellationToken: cancellationToken));
}
}