Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c9b59994b5 |
@@ -22,33 +22,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.x'
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
|
||||||
with:
|
|
||||||
version: 10
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
|
||||||
|
|
||||||
- name: Build frontend into Host static assets
|
|
||||||
run: |
|
|
||||||
pnpm install --frozen-lockfile
|
|
||||||
VERSION_DATE="$(TZ=Asia/Seoul date +%Y.%m.%d)"
|
|
||||||
RELEASE_COUNT="$(git ls-remote --tags origin "refs/tags/v${VERSION_DATE}.*" | wc -l | tr -d ' ')"
|
|
||||||
VERSION_SEQUENCE="$((RELEASE_COUNT + 1))"
|
|
||||||
APP_VERSION="${VERSION_DATE}.${VERSION_SEQUENCE}.${GITHUB_SHA::10}"
|
|
||||||
echo "VITE_APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
|
|
||||||
echo "release_version=${APP_VERSION}"
|
|
||||||
VITE_APP_VERSION="${APP_VERSION}" pnpm build
|
|
||||||
grep -R -q 'app-version' dist
|
|
||||||
grep -R -q 'UI contract 4.0' dist
|
|
||||||
grep -R -q "${APP_VERSION}" dist
|
|
||||||
find ../src/KArtSell.Host/wwwroot -mindepth 1 -delete
|
|
||||||
cp -R dist/. ../src/KArtSell.Host/wwwroot/
|
|
||||||
working-directory: frontend
|
|
||||||
|
|
||||||
- run: dotnet restore KArtSell.sln
|
- run: dotnet restore KArtSell.sln
|
||||||
|
|
||||||
- run: dotnet build KArtSell.sln --no-restore -c Release
|
- run: dotnet build KArtSell.sln --no-restore -c Release
|
||||||
@@ -85,11 +58,6 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
ssh -i /tmp/deploy_key.pem -o StrictHostKeyChecking=no kjh2064@178.104.200.7 \
|
ssh -i /tmp/deploy_key.pem -o StrictHostKeyChecking=no kjh2064@178.104.200.7 \
|
||||||
"set -euo pipefail; \
|
"set -euo pipefail; \
|
||||||
sudo -n -l | grep -Fq '/usr/bin/systemctl restart kartsell' || { \
|
|
||||||
echo 'Deployment blocked: one-time sudoers delegation is missing for kartsell.' >&2; \
|
|
||||||
echo 'Expected: kjh2064 ALL=(root) NOPASSWD: /usr/bin/systemctl restart kartsell' >&2; \
|
|
||||||
exit 77; \
|
|
||||||
}; \
|
|
||||||
export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'; \
|
export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'; \
|
||||||
mkdir -p /app/kartsell/current; \
|
mkdir -p /app/kartsell/current; \
|
||||||
unzip -oq /tmp/kartsell-release.zip -d /app/kartsell/current; \
|
unzip -oq /tmp/kartsell-release.zip -d /app/kartsell/current; \
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 배포 frontend artifact 계약
|
|
||||||
|
|
||||||
## Source
|
|
||||||
|
|
||||||
- 운영 배포 Run 3357 로그: `dotnet publish` 전 frontend build 단계 없음
|
|
||||||
- 운영 bundle에 `app-version` 및 `UI contract 4.0` marker 없음
|
|
||||||
- `frontend`의 재현 가능한 `pnpm-lock.yaml` 및 기존 CI frontend job
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
배포 workflow는 Host publish 전에 다음 규칙으로 버전을 계산하고 frontend를 재생성한다.
|
|
||||||
|
|
||||||
```text
|
|
||||||
YYYY.MM.DD.<당일 release 순번>.<commit SHA 10자리>
|
|
||||||
```
|
|
||||||
|
|
||||||
당일 순번은 `vYYYY.MM.DD.*` release tag 개수에 1을 더해 계산한다. 예: `2026.08.06.1.acaa731b3f`. 생성된 `frontend/dist`를 Host `wwwroot`에 복사하고, `app-version`, `UI contract 4.0`, 계산된 전체 버전 marker가 없으면 배포를 중단한다.
|
|
||||||
|
|
||||||
## Evidence / Unknown
|
|
||||||
|
|
||||||
- Source 변경과 운영 artifact를 분리하지 않고, 매 배포 시 동일 commit에서 재생성한다.
|
|
||||||
- 실제 운영 반영 증거는 이 Slice의 CI 및 deploy run 완료 후 보존한다.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# KArtSell 배포 재기동 권한 계약
|
|
||||||
|
|
||||||
## Source
|
|
||||||
|
|
||||||
- 운영 호스트 `hz-prod-01`의 실제 sudo 정책 조회 결과
|
|
||||||
- 기존 `quantengine` 및 `taxbaik` 서비스의 특정 `systemctl restart` `NOPASSWD` 위임 패턴
|
|
||||||
- `.gitea/workflows/deploy.yml`
|
|
||||||
|
|
||||||
## Assumption
|
|
||||||
|
|
||||||
- 배포 SSH 계정은 `kjh2064`로 유지한다.
|
|
||||||
- 운영 서비스는 `/etc/systemd/system/kartsell.service`로 유지한다.
|
|
||||||
- DbMigrator와 artifact 복사는 현재처럼 `kjh2064` 권한으로 수행한다.
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
`kjh2064`에 전체 sudo 권한을 부여하지 않고, 운영자가 한 번만 다음 단일 명령을 `/etc/sudoers.d/kartsell-deploy`에 등록한다.
|
|
||||||
|
|
||||||
```sudoers
|
|
||||||
kjh2064 ALL=(root) NOPASSWD: /usr/bin/systemctl restart kartsell
|
|
||||||
```
|
|
||||||
|
|
||||||
파일 권한은 `0440`이어야 하며 `visudo -cf /etc/sudoers.d/kartsell-deploy` 검증 후 적용한다. 이후 CI는 비대화형 `sudo -n systemctl restart kartsell`만 사용하므로 배포마다 비밀번호 입력이나 sudo 등록이 필요 없다.
|
|
||||||
|
|
||||||
## Deployment guard
|
|
||||||
|
|
||||||
워크플로우는 artifact 복사와 DbMigrator 실행 전에 `sudo -n -l`로 위임 존재 여부를 검사한다. 위임이 없으면 운영 DB를 변경하지 않고 exit 77로 종료한다.
|
|
||||||
|
|
||||||
## Unknown / Decision Required
|
|
||||||
|
|
||||||
- 이 파일을 운영 호스트에 설치할 권한은 root 운영자에게만 있다.
|
|
||||||
- 설치 후 필요한 증거: `visudo -cf` 결과, `sudo -n -l` 결과, 다음 deploy run의 성공 로그, 서비스 active 상태.
|
|
||||||
@@ -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.
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
import { ref } from 'vue';
|
|
||||||
import { KsStatusTag } from '@/shared/ui/components';
|
|
||||||
const selectedId = ref('UI-001');
|
|
||||||
const items = [
|
|
||||||
{ id: 'UI-001', title: '공유 컴포넌트 카탈로그와 상태 프리뷰', owner: 'FE Platform', state: 'IN_PROGRESS' },
|
|
||||||
{ id: 'UI-002', title: 'WBS 실행 화면 및 요구사항 추적', owner: 'Delivery', state: 'IN_PROGRESS' },
|
|
||||||
{ id: 'DATA-001', title: 'DB 스키마 Read Model/API 계약', owner: 'Data Platform', state: 'DECISION_REQUIRED' },
|
|
||||||
{ id: 'OPS-001', title: 'Playwright 시각·상태행렬 검증', owner: 'QA', state: 'IN_PROGRESS' },
|
|
||||||
];
|
|
||||||
const __VLS_ctx = {
|
|
||||||
...{},
|
|
||||||
...{},
|
|
||||||
};
|
|
||||||
let __VLS_components;
|
|
||||||
let __VLS_intrinsics;
|
|
||||||
let __VLS_directives;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['page-header']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['list']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['detail']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['detail']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['detail']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['detail']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['workspace']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['page-header']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({
|
|
||||||
...{ class: "page" },
|
|
||||||
'aria-labelledby': "wbs-title",
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['page']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({
|
|
||||||
...{ class: "page-header" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['page-header']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
|
||||||
...{ class: "eyebrow" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['eyebrow']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({
|
|
||||||
id: "wbs-title",
|
|
||||||
});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
|
|
||||||
let __VLS_0;
|
|
||||||
/** @ts-ignore @type { | typeof __VLS_components.KsStatusTag} */
|
|
||||||
KsStatusTag;
|
|
||||||
// @ts-ignore
|
|
||||||
const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({
|
|
||||||
value: "AUTOMATION OFF",
|
|
||||||
severity: "warning",
|
|
||||||
}));
|
|
||||||
const __VLS_2 = __VLS_1({
|
|
||||||
value: "AUTOMATION OFF",
|
|
||||||
severity: "warning",
|
|
||||||
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
|
||||||
...{ class: "summary-grid" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['summary-grid']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
|
||||||
...{ class: "ks-card" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['ks-card']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
|
||||||
...{ class: "ks-card" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['ks-card']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
|
||||||
...{ class: "ks-card" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['ks-card']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.strong, __VLS_intrinsics.strong)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
|
|
||||||
...{ class: "workspace" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['workspace']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({
|
|
||||||
...{ class: "ks-card list" },
|
|
||||||
'aria-labelledby': "wbs-list-title",
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['ks-card']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['list']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({
|
|
||||||
id: "wbs-list-title",
|
|
||||||
});
|
|
||||||
for (const [item] of __VLS_vFor((__VLS_ctx.items))) {
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.button, __VLS_intrinsics.button)({
|
|
||||||
...{ onClick: (...[$event]) => {
|
|
||||||
return (__VLS_ctx.selectedId = item.id);
|
|
||||||
// @ts-ignore
|
|
||||||
[items, selectedId,];
|
|
||||||
} },
|
|
||||||
key: (item.id),
|
|
||||||
...{ class: "wbs-row" },
|
|
||||||
...{ class: ({ selected: __VLS_ctx.selectedId === item.id }) },
|
|
||||||
type: "button",
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['wbs-row']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['selected']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.span, __VLS_intrinsics.span)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.b, __VLS_intrinsics.b)({});
|
|
||||||
(item.id);
|
|
||||||
(item.title);
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.small, __VLS_intrinsics.small)({});
|
|
||||||
(item.owner);
|
|
||||||
let __VLS_5;
|
|
||||||
/** @ts-ignore @type { | typeof __VLS_components.KsStatusTag} */
|
|
||||||
KsStatusTag;
|
|
||||||
// @ts-ignore
|
|
||||||
const __VLS_6 = __VLS_asFunctionalComponent1(__VLS_5, new __VLS_5({
|
|
||||||
value: (item.state),
|
|
||||||
severity: (item.state === 'DONE' ? 'success' : item.state === 'DECISION_REQUIRED' ? 'danger' : 'info'),
|
|
||||||
}));
|
|
||||||
const __VLS_7 = __VLS_6({
|
|
||||||
value: (item.state),
|
|
||||||
severity: (item.state === 'DONE' ? 'success' : item.state === 'DECISION_REQUIRED' ? 'danger' : 'info'),
|
|
||||||
}, ...__VLS_functionalComponentArgsRest(__VLS_6));
|
|
||||||
// @ts-ignore
|
|
||||||
[selectedId,];
|
|
||||||
}
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({
|
|
||||||
...{ class: "ks-card detail" },
|
|
||||||
'aria-labelledby': "wbs-detail-title",
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['ks-card']} */ ;
|
|
||||||
/** @type {__VLS_StyleScopedClasses['detail']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({
|
|
||||||
id: "wbs-detail-title",
|
|
||||||
});
|
|
||||||
if (__VLS_ctx.selectedId === 'DATA-001') {
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({
|
|
||||||
...{ class: "warning" },
|
|
||||||
});
|
|
||||||
/** @type {__VLS_StyleScopedClasses['warning']} */ ;
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
|
|
||||||
(__VLS_ctx.selectedId);
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
|
|
||||||
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
[selectedId, selectedId,];
|
|
||||||
const __VLS_export = (await import('vue')).defineComponent({});
|
|
||||||
export default {};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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