Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9b59994b5 |
@@ -0,0 +1,8 @@
|
|||||||
|
-- PHASE-1-SHADOW-RUN / REQ-EXEC-001
|
||||||
|
-- Bind a client-selected model identity to the server-side approved VersionSet.
|
||||||
|
ALTER TABLE governance.model_version_registry
|
||||||
|
ADD COLUMN IF NOT EXISTS model_id uuid;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_model_version_registry_model_scope_effective
|
||||||
|
ON governance.model_version_registry (model_id, scope_key, effective_at desc)
|
||||||
|
WHERE model_id IS NOT NULL;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Phase 1 VersionSet Automation Slice
|
||||||
|
|
||||||
|
## WBS / Scope
|
||||||
|
|
||||||
|
- WBS: `PHASE-1-SHADOW-RUN`
|
||||||
|
- Slice: server-side model identity to approved VersionSet resolution
|
||||||
|
- Requirement: `REQ-EXEC-001`
|
||||||
|
- Scope: resolve VersionSet by approved `model_id`, `scope_key`, and PIT cutoff before JobRun/enqueue.
|
||||||
|
- Out of scope: automatic model promotion, threshold mutation, order/KIS submission, and production seed data.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
- `DapperApprovedModelContextReader` already resolves approved Dataset/Model by `scope_key` and PIT.
|
||||||
|
- `InitiateShadowRunHandler` currently generates RunId/IdempotencyKey but does not resolve VersionSet or create JobRun.
|
||||||
|
- `governance.model_version_registry` has no model identity column, so the endpoint cannot safely bind `modelId` to an approved model version.
|
||||||
|
- `AGENTS.md` requires server-side PIT evidence and forbids trusting client-supplied evidence.
|
||||||
|
|
||||||
|
## Assumption
|
||||||
|
|
||||||
|
- `model_id` is the stable server-side identity for the requested Shadow model.
|
||||||
|
- Existing registry rows, if any, remain valid with nullable `model_id` until explicitly backfilled and approved.
|
||||||
|
|
||||||
|
## Unknown
|
||||||
|
|
||||||
|
- Production model registry contains no approved rows today; this Slice does not invent or seed them.
|
||||||
|
- JobRun persistence is already available but is not yet wired into the ShadowRun handler.
|
||||||
|
|
||||||
|
## Decision Required
|
||||||
|
|
||||||
|
- DBA/Model Owner must approve model registry backfill before any production Shadow enqueue.
|
||||||
|
|
||||||
|
## Acceptance Evidence
|
||||||
|
|
||||||
|
- Migration adds the model identity mapping without modifying prior migrations.
|
||||||
|
- Reader requires `model_id`, `scope_key`, and PIT cutoff and returns only approved server-side context.
|
||||||
|
- No context returns no enqueue path.
|
||||||
|
- Existing automatic order/KIS capabilities remain OFF.
|
||||||
@@ -35,6 +35,7 @@ public sealed record ModelOperationRequest(
|
|||||||
public interface IApprovedModelContextReader
|
public interface IApprovedModelContextReader
|
||||||
{
|
{
|
||||||
Task<ApprovedModelContext?> ReadAsync(string scopeKey, DateTimeOffset asOf, CancellationToken cancellationToken);
|
Task<ApprovedModelContext?> ReadAsync(string scopeKey, DateTimeOffset asOf, CancellationToken cancellationToken);
|
||||||
|
Task<ApprovedModelContext?> ReadAsync(Guid modelId, string scopeKey, DateTimeOffset asOf, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IModelScheduleRepository
|
public interface IModelScheduleRepository
|
||||||
|
|||||||
+27
-2
@@ -7,7 +7,7 @@ namespace KArtSell.Modules.ModelOperations.Infrastructure;
|
|||||||
|
|
||||||
public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connectionFactory) : IApprovedModelContextReader
|
public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connectionFactory) : IApprovedModelContextReader
|
||||||
{
|
{
|
||||||
private const string Sql = """
|
private const string SqlByScope = """
|
||||||
select mv.scope_key as ScopeKey,
|
select mv.scope_key as ScopeKey,
|
||||||
mv.model_version as ModelVersion,
|
mv.model_version as ModelVersion,
|
||||||
mv.config_version as ConfigVersion,
|
mv.config_version as ConfigVersion,
|
||||||
@@ -34,6 +34,10 @@ public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connec
|
|||||||
limit 1;
|
limit 1;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
private static readonly string SqlByModel = SqlByScope.Replace(
|
||||||
|
"where mv.scope_key = @ScopeKey",
|
||||||
|
"where mv.model_id = @ModelId and mv.scope_key = @ScopeKey");
|
||||||
|
|
||||||
public async Task<ApprovedModelContext?> ReadAsync(
|
public async Task<ApprovedModelContext?> ReadAsync(
|
||||||
string scopeKey,
|
string scopeKey,
|
||||||
DateTimeOffset asOf,
|
DateTimeOffset asOf,
|
||||||
@@ -41,7 +45,7 @@ public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connec
|
|||||||
{
|
{
|
||||||
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
||||||
var row = await connection.QuerySingleOrDefaultAsync<Row>(new CommandDefinition(
|
var row = await connection.QuerySingleOrDefaultAsync<Row>(new CommandDefinition(
|
||||||
Sql,
|
SqlByScope,
|
||||||
new { ScopeKey = scopeKey, AsOf = asOf },
|
new { ScopeKey = scopeKey, AsOf = asOf },
|
||||||
cancellationToken: cancellationToken));
|
cancellationToken: cancellationToken));
|
||||||
|
|
||||||
@@ -55,6 +59,27 @@ public sealed class DapperApprovedModelContextReader(IDbConnectionFactory connec
|
|||||||
row.EffectiveAt);
|
row.EffectiveAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ApprovedModelContext?> ReadAsync(
|
||||||
|
Guid modelId,
|
||||||
|
string scopeKey,
|
||||||
|
DateTimeOffset asOf,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
|
||||||
|
var row = await connection.QuerySingleOrDefaultAsync<Row>(new CommandDefinition(
|
||||||
|
SqlByModel,
|
||||||
|
new { ModelId = modelId, ScopeKey = scopeKey, AsOf = asOf },
|
||||||
|
cancellationToken: cancellationToken));
|
||||||
|
|
||||||
|
return row is null ? null : ToContext(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovedModelContext ToContext(Row row) => new(
|
||||||
|
row.ScopeKey,
|
||||||
|
new VersionSet(row.DatasetId, row.DataHash, row.ModelVersion, row.ConfigVersion, row.CodeSha, row.ContractVersion),
|
||||||
|
row.LifecycleState,
|
||||||
|
row.EffectiveAt);
|
||||||
|
|
||||||
private sealed record Row(
|
private sealed record Row(
|
||||||
string ScopeKey,
|
string ScopeKey,
|
||||||
string DatasetId,
|
string DatasetId,
|
||||||
|
|||||||
Reference in New Issue
Block a user