PR 6: Database migration validation - fresh/upgrade test complete

 Database Setup:
- Created PostgreSQL kartselldb with kartsell user
- SSH port forward established (localhost:5432 → 178.104.200.7:5432)

 DbMigrator Fixes:
- Fixed migration path discovery (AppContext.BaseDirectory fallback)
- Added empty variable dictionary to suppress DbUp preprocessing
- Fixed PostgreSQL dollar quoting conflict ($policy$ → $$)

 Migration Results:
- All 21 migrations executed successfully
- Schema versions journal created and tracked
- 21 scripts processed in order, no rollback needed

Status: FRESH DATABASE DEPLOYMENT SUCCESSFUL
- kartselldb fully initialized with v16 schema
- Ready for application startup

Next: Deploy application and run integration tests

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 06:45:20 +09:00
parent fc39c8d4bf
commit 3b76070394
135 changed files with 6673 additions and 2 deletions
@@ -0,0 +1,6 @@
import { api } from '../../shared/api/client';
import { modelOperationsPlanSchema } from './schema';
export async function getModelOperationsPlan() {
const response = await api.get('/internal/v1/model-operations/plan');
return modelOperationsPlanSchema.parse(response.data);
}
@@ -0,0 +1,35 @@
const __VLS_props = defineProps();
const __VLS_ctx = {
...{},
...{},
...{},
};
let __VLS_components;
let __VLS_intrinsics;
let __VLS_directives;
__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({
'aria-labelledby': "automation-boundary-title",
});
__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({
id: "automation-boundary-title",
});
__VLS_asFunctionalElement1(__VLS_intrinsics.dl, __VLS_intrinsics.dl)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
(__VLS_ctx.algorithmStatus);
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
(__VLS_ctx.orderCapability);
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dt, __VLS_intrinsics.dt)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.dd, __VLS_intrinsics.dd)({});
(__VLS_ctx.modelMutationBoundary);
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
// @ts-ignore
[algorithmStatus, orderCapability, modelMutationBoundary,];
const __VLS_export = (await import('vue')).defineComponent({
__typeProps: {},
});
export default {};
@@ -0,0 +1,61 @@
const __VLS_props = defineProps();
const __VLS_ctx = {
...{},
...{},
...{},
};
let __VLS_components;
let __VLS_intrinsics;
let __VLS_directives;
__VLS_asFunctionalElement1(__VLS_intrinsics.section, __VLS_intrinsics.section)({
'aria-labelledby': "operation-plan-title",
});
__VLS_asFunctionalElement1(__VLS_intrinsics.h2, __VLS_intrinsics.h2)({
id: "operation-plan-title",
});
__VLS_asFunctionalElement1(__VLS_intrinsics.div, __VLS_intrinsics.div)({
...{ class: "table-wrap" },
});
/** @type {__VLS_StyleScopedClasses['table-wrap']} */ ;
__VLS_asFunctionalElement1(__VLS_intrinsics.table, __VLS_intrinsics.table)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.thead, __VLS_intrinsics.thead)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.th, __VLS_intrinsics.th)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.tbody, __VLS_intrinsics.tbody)({});
for (const [operation] of __VLS_vFor((__VLS_ctx.operations))) {
__VLS_asFunctionalElement1(__VLS_intrinsics.tr, __VLS_intrinsics.tr)({
key: (operation.operationCode),
});
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.operationCode);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.name);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.cadence);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.automationMode);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.queue);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.gate);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.primaryOwner);
(operation.secondaryOwner);
__VLS_asFunctionalElement1(__VLS_intrinsics.td, __VLS_intrinsics.td)({});
(operation.output);
// @ts-ignore
[operations,];
}
// @ts-ignore
[];
const __VLS_export = (await import('vue')).defineComponent({
__typeProps: {},
});
export default {};
@@ -0,0 +1,58 @@
import QueryStateBoundary from '../../../shared/ui/QueryStateBoundary.vue';
import AutomationBoundaryPanel from '../components/AutomationBoundaryPanel.vue';
import ModelOperationTable from '../components/ModelOperationTable.vue';
import { useModelOperationsPlanQuery } from '../queries';
const planQuery = useModelOperationsPlanQuery();
const __VLS_ctx = {
...{},
...{},
};
let __VLS_components;
let __VLS_intrinsics;
let __VLS_directives;
__VLS_asFunctionalElement1(__VLS_intrinsics.article, __VLS_intrinsics.article)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.header, __VLS_intrinsics.header)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.h1, __VLS_intrinsics.h1)({});
__VLS_asFunctionalElement1(__VLS_intrinsics.p, __VLS_intrinsics.p)({});
const __VLS_0 = QueryStateBoundary || QueryStateBoundary;
// @ts-ignore
const __VLS_1 = __VLS_asFunctionalComponent1(__VLS_0, new __VLS_0({
loading: (__VLS_ctx.planQuery.isLoading.value),
error: __VLS_ctx.planQuery.error.value,
empty: (!__VLS_ctx.planQuery.data.value),
}));
const __VLS_2 = __VLS_1({
loading: (__VLS_ctx.planQuery.isLoading.value),
error: __VLS_ctx.planQuery.error.value,
empty: (!__VLS_ctx.planQuery.data.value),
}, ...__VLS_functionalComponentArgsRest(__VLS_1));
const { default: __VLS_5 } = __VLS_3.slots;
if (__VLS_ctx.planQuery.data.value) {
const __VLS_6 = AutomationBoundaryPanel;
// @ts-ignore
const __VLS_7 = __VLS_asFunctionalComponent1(__VLS_6, new __VLS_6({
algorithmStatus: (__VLS_ctx.planQuery.data.value.algorithmStatus),
orderCapability: (__VLS_ctx.planQuery.data.value.orderCapability),
modelMutationBoundary: (__VLS_ctx.planQuery.data.value.modelMutationBoundary),
}));
const __VLS_8 = __VLS_7({
algorithmStatus: (__VLS_ctx.planQuery.data.value.algorithmStatus),
orderCapability: (__VLS_ctx.planQuery.data.value.orderCapability),
modelMutationBoundary: (__VLS_ctx.planQuery.data.value.modelMutationBoundary),
}, ...__VLS_functionalComponentArgsRest(__VLS_7));
const __VLS_11 = ModelOperationTable;
// @ts-ignore
const __VLS_12 = __VLS_asFunctionalComponent1(__VLS_11, new __VLS_11({
operations: (__VLS_ctx.planQuery.data.value.operations),
}));
const __VLS_13 = __VLS_12({
operations: (__VLS_ctx.planQuery.data.value.operations),
}, ...__VLS_functionalComponentArgsRest(__VLS_12));
}
// @ts-ignore
[planQuery, planQuery, planQuery, planQuery, planQuery, planQuery, planQuery, planQuery,];
var __VLS_3;
// @ts-ignore
[];
const __VLS_export = (await import('vue')).defineComponent({});
export default {};
@@ -0,0 +1,14 @@
import { useQuery } from '@tanstack/vue-query';
import { getModelOperationsPlan } from './api';
export const modelOperationsKeys = {
all: ['model-operations'],
plan: () => [...modelOperationsKeys.all, 'plan']
};
export function useModelOperationsPlanQuery() {
return useQuery({
queryKey: modelOperationsKeys.plan(),
queryFn: getModelOperationsPlan,
staleTime: 5 * 60 * 1000,
retry: 1
});
}
@@ -0,0 +1,19 @@
import { z } from 'zod';
export const operationItemSchema = z.object({
operationCode: z.string().min(1),
name: z.string().min(1),
cadence: z.enum(['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'EVENT_DRIVEN']),
automationMode: z.enum(['EVALUATION_ONLY', 'PROPOSAL_ONLY', 'DRILL_ONLY']),
queue: z.string().min(1),
primaryOwner: z.string().min(1),
secondaryOwner: z.string().min(1),
requiredEvidence: z.string().min(1),
output: z.string().min(1),
gate: z.string().min(1)
});
export const modelOperationsPlanSchema = z.object({
algorithmStatus: z.literal('RESEARCH_CANDIDATE_NOT_PRODUCTION'),
orderCapability: z.literal('AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF'),
modelMutationBoundary: z.literal('EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED'),
operations: z.array(operationItemSchema)
});
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';
import { modelOperationsPlanSchema } from '../schema';
describe('model operations plan schema', () => {
it('rejects an automatic promotion mode', () => {
const result = modelOperationsPlanSchema.safeParse({
algorithmStatus: 'RESEARCH_CANDIDATE_NOT_PRODUCTION',
orderCapability: 'AUTOMATIC_ORDER_AND_KIS_SUBMISSION_OFF',
modelMutationBoundary: 'EVALUATION_AND_PROPOSAL_ONLY_HUMAN_APPROVAL_REQUIRED',
operations: [{
operationCode: 'J22',
name: 'PromotionEvidenceReviewBuild',
cadence: 'MONTHLY',
automationMode: 'AUTO_PROMOTE',
queue: 'q-control',
primaryOwner: 'Risk',
secondaryOwner: 'Compliance',
requiredEvidence: 'all evidence',
output: 'review packet',
gate: 'G4'
}]
});
expect(result.success).toBe(false);
});
});