@proofhound/core 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/channels/mcp/annotation.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/annotation.tools.js +18 -5
- package/dist/server/channels/mcp/annotation.tools.js.map +1 -1
- package/dist/server/channels/mcp/canary-release.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/canary-release.tools.js +2 -1
- package/dist/server/channels/mcp/canary-release.tools.js.map +1 -1
- package/dist/server/channels/mcp/dataset.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/dataset.tools.js +49 -1
- package/dist/server/channels/mcp/dataset.tools.js.map +1 -1
- package/dist/server/channels/mcp/mcp-server.factory.d.ts +9 -1
- package/dist/server/channels/mcp/mcp-server.factory.d.ts.map +1 -1
- package/dist/server/channels/mcp/mcp-server.factory.js +28 -3
- package/dist/server/channels/mcp/mcp-server.factory.js.map +1 -1
- package/dist/server/channels/mcp/prompt.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/prompt.tools.js +34 -2
- package/dist/server/channels/mcp/prompt.tools.js.map +1 -1
- package/dist/server/channels/mcp/release-line.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/release-line.tools.js +292 -4
- package/dist/server/channels/mcp/release-line.tools.js.map +1 -1
- package/dist/server/channels/mcp/run-result.tools.d.ts.map +1 -1
- package/dist/server/channels/mcp/run-result.tools.js +7 -5
- package/dist/server/channels/mcp/run-result.tools.js.map +1 -1
- package/dist/server/infrastructure/llm/run-result-writer.js +3 -3
- package/dist/server/modules/annotation/annotation.controller.d.ts +28 -13
- package/dist/server/modules/annotation/annotation.controller.d.ts.map +1 -1
- package/dist/server/modules/annotation/annotation.repository.d.ts +6 -2
- package/dist/server/modules/annotation/annotation.repository.d.ts.map +1 -1
- package/dist/server/modules/annotation/annotation.repository.js +340 -96
- package/dist/server/modules/annotation/annotation.repository.js.map +1 -1
- package/dist/server/modules/annotation/annotation.service.d.ts.map +1 -1
- package/dist/server/modules/annotation/annotation.service.js +62 -10
- package/dist/server/modules/annotation/annotation.service.js.map +1 -1
- package/dist/server/modules/canary-release/canary-release.controller.d.ts +63 -42
- package/dist/server/modules/canary-release/canary-release.controller.d.ts.map +1 -1
- package/dist/server/modules/canary-release/canary-release.repository.d.ts +23 -5
- package/dist/server/modules/canary-release/canary-release.repository.d.ts.map +1 -1
- package/dist/server/modules/canary-release/canary-release.repository.js +28 -12
- package/dist/server/modules/canary-release/canary-release.repository.js.map +1 -1
- package/dist/server/modules/canary-release/canary-release.service.d.ts.map +1 -1
- package/dist/server/modules/canary-release/canary-release.service.js +32 -10
- package/dist/server/modules/canary-release/canary-release.service.js.map +1 -1
- package/dist/server/modules/canary-release/canary-runtime.d.ts +11 -1
- package/dist/server/modules/canary-release/canary-runtime.d.ts.map +1 -1
- package/dist/server/modules/canary-release/canary-runtime.js +63 -8
- package/dist/server/modules/canary-release/canary-runtime.js.map +1 -1
- package/dist/server/modules/dataset/dataset-deletion.hook.d.ts +16 -0
- package/dist/server/modules/dataset/dataset-deletion.hook.d.ts.map +1 -0
- package/dist/server/modules/dataset/dataset-deletion.hook.js +57 -0
- package/dist/server/modules/dataset/dataset-deletion.hook.js.map +1 -0
- package/dist/server/modules/dataset/dataset-import.controller.d.ts +2 -0
- package/dist/server/modules/dataset/dataset-import.controller.d.ts.map +1 -1
- package/dist/server/modules/dataset/dataset.controller.d.ts +98 -0
- package/dist/server/modules/dataset/dataset.controller.d.ts.map +1 -1
- package/dist/server/modules/dataset/dataset.controller.js +36 -0
- package/dist/server/modules/dataset/dataset.controller.js.map +1 -1
- package/dist/server/modules/dataset/dataset.module.d.ts.map +1 -1
- package/dist/server/modules/dataset/dataset.module.js +8 -1
- package/dist/server/modules/dataset/dataset.module.js.map +1 -1
- package/dist/server/modules/dataset/dataset.repository.d.ts +19 -0
- package/dist/server/modules/dataset/dataset.repository.d.ts.map +1 -1
- package/dist/server/modules/dataset/dataset.repository.js +248 -9
- package/dist/server/modules/dataset/dataset.repository.js.map +1 -1
- package/dist/server/modules/dataset/dataset.service.d.ts +33 -1
- package/dist/server/modules/dataset/dataset.service.d.ts.map +1 -1
- package/dist/server/modules/dataset/dataset.service.js +49 -7
- package/dist/server/modules/dataset/dataset.service.js.map +1 -1
- package/dist/server/modules/experiment/experiment.controller.d.ts +8 -8
- package/dist/server/modules/experiment/experiment.repository.d.ts.map +1 -1
- package/dist/server/modules/experiment/experiment.repository.js +28 -0
- package/dist/server/modules/experiment/experiment.repository.js.map +1 -1
- package/dist/server/modules/experiment/experiment.service.d.ts.map +1 -1
- package/dist/server/modules/experiment/experiment.service.js +6 -3
- package/dist/server/modules/experiment/experiment.service.js.map +1 -1
- package/dist/server/modules/model/project-model.controller.d.ts +5 -5
- package/dist/server/modules/monitoring/monitoring.repository.js +1 -1
- package/dist/server/modules/optimization/optimization.controller.d.ts +12 -12
- package/dist/server/modules/optimization/optimization.repository.d.ts +6 -0
- package/dist/server/modules/optimization/optimization.repository.d.ts.map +1 -1
- package/dist/server/modules/optimization/optimization.repository.js +96 -4
- package/dist/server/modules/optimization/optimization.repository.js.map +1 -1
- package/dist/server/modules/optimization/optimization.service.d.ts.map +1 -1
- package/dist/server/modules/optimization/optimization.service.js +13 -4
- package/dist/server/modules/optimization/optimization.service.js.map +1 -1
- package/dist/server/modules/optimization/optimization.workflow.js +1 -1
- package/dist/server/modules/optimization/optimization.workflow.js.map +1 -1
- package/dist/server/modules/production-release/production-release.controller.d.ts +12 -9
- package/dist/server/modules/production-release/production-release.controller.d.ts.map +1 -1
- package/dist/server/modules/production-release/production-release.repository.d.ts +2 -1
- package/dist/server/modules/production-release/production-release.repository.d.ts.map +1 -1
- package/dist/server/modules/production-release/production-release.repository.js +3 -1
- package/dist/server/modules/production-release/production-release.repository.js.map +1 -1
- package/dist/server/modules/production-release/production-release.service.d.ts.map +1 -1
- package/dist/server/modules/production-release/production-release.service.js +10 -1
- package/dist/server/modules/production-release/production-release.service.js.map +1 -1
- package/dist/server/modules/prompt/prompt-deletion.hook.d.ts +18 -0
- package/dist/server/modules/prompt/prompt-deletion.hook.d.ts.map +1 -0
- package/dist/server/modules/prompt/prompt-deletion.hook.js +69 -0
- package/dist/server/modules/prompt/prompt-deletion.hook.js.map +1 -0
- package/dist/server/modules/prompt/prompt.controller.d.ts +146 -38
- package/dist/server/modules/prompt/prompt.controller.d.ts.map +1 -1
- package/dist/server/modules/prompt/prompt.controller.js +24 -0
- package/dist/server/modules/prompt/prompt.controller.js.map +1 -1
- package/dist/server/modules/prompt/prompt.module.d.ts.map +1 -1
- package/dist/server/modules/prompt/prompt.module.js +7 -1
- package/dist/server/modules/prompt/prompt.module.js.map +1 -1
- package/dist/server/modules/prompt/prompt.repository.d.ts +33 -3
- package/dist/server/modules/prompt/prompt.repository.d.ts.map +1 -1
- package/dist/server/modules/prompt/prompt.repository.js +267 -39
- package/dist/server/modules/prompt/prompt.repository.js.map +1 -1
- package/dist/server/modules/prompt/prompt.service.d.ts +78 -6
- package/dist/server/modules/prompt/prompt.service.d.ts.map +1 -1
- package/dist/server/modules/prompt/prompt.service.js +79 -49
- package/dist/server/modules/prompt/prompt.service.js.map +1 -1
- package/dist/server/modules/quick-start/quick-start.controller.d.ts +1 -1
- package/dist/server/modules/quick-start/quick-start.service.d.ts +1 -1
- package/dist/server/modules/release-line/release-line-deletion.hook.d.ts +16 -0
- package/dist/server/modules/release-line/release-line-deletion.hook.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-line-deletion.hook.js +60 -0
- package/dist/server/modules/release-line/release-line-deletion.hook.js.map +1 -0
- package/dist/server/modules/release-line/release-line.controller.d.ts +2503 -82
- package/dist/server/modules/release-line/release-line.controller.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-line.controller.js +169 -0
- package/dist/server/modules/release-line/release-line.controller.js.map +1 -1
- package/dist/server/modules/release-line/release-line.module.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-line.module.js +8 -1
- package/dist/server/modules/release-line/release-line.module.js.map +1 -1
- package/dist/server/modules/release-line/release-line.repository.d.ts +55 -3
- package/dist/server/modules/release-line/release-line.repository.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-line.repository.js +797 -111
- package/dist/server/modules/release-line/release-line.repository.js.map +1 -1
- package/dist/server/modules/release-line/release-line.service.d.ts +22 -5
- package/dist/server/modules/release-line/release-line.service.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-line.service.js +231 -3
- package/dist/server/modules/release-line/release-line.service.js.map +1 -1
- package/dist/server/modules/release-line/release-runner.repository.d.ts +2 -1
- package/dist/server/modules/release-line/release-runner.repository.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-runner.repository.js +14 -10
- package/dist/server/modules/release-line/release-runner.repository.js.map +1 -1
- package/dist/server/modules/release-line/release-runner.service.d.ts +2 -1
- package/dist/server/modules/release-line/release-runner.service.d.ts.map +1 -1
- package/dist/server/modules/release-line/release-runner.service.js +105 -11
- package/dist/server/modules/release-line/release-runner.service.js.map +1 -1
- package/dist/server/modules/release-line/release-variable-mapping.d.ts +9 -0
- package/dist/server/modules/release-line/release-variable-mapping.d.ts.map +1 -0
- package/dist/server/modules/release-line/release-variable-mapping.js +83 -0
- package/dist/server/modules/release-line/release-variable-mapping.js.map +1 -0
- package/dist/server/modules/run-result/run-result.controller.d.ts +10 -7
- package/dist/server/modules/run-result/run-result.controller.d.ts.map +1 -1
- package/dist/server/modules/run-result/run-result.repository.d.ts.map +1 -1
- package/dist/server/modules/run-result/run-result.repository.js +43 -18
- package/dist/server/modules/run-result/run-result.repository.js.map +1 -1
- package/dist/webhook/channels/webhook/webhook.controller.d.ts +4 -0
- package/dist/webhook/channels/webhook/webhook.controller.d.ts.map +1 -1
- package/dist/webhook/channels/webhook/webhook.service.d.ts +2 -0
- package/dist/webhook/channels/webhook/webhook.service.d.ts.map +1 -1
- package/dist/webhook/channels/webhook/webhook.service.js +6 -0
- package/dist/webhook/channels/webhook/webhook.service.js.map +1 -1
- package/dist/worker/consumers/llm.consumer.js +3 -3
- package/dist/worker/consumers/llm.consumer.js.map +1 -1
- package/dist/worker/runners/llm-runner.d.ts.map +1 -1
- package/dist/worker/runners/llm-runner.js +9 -1
- package/dist/worker/runners/llm-runner.js.map +1 -1
- package/dist/worker/runners/run-result-writer.js +3 -3
- package/package.json +12 -12
|
@@ -17,7 +17,7 @@ const common_1 = require("@nestjs/common");
|
|
|
17
17
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
18
18
|
const db_1 = require("@proofhound/db");
|
|
19
19
|
const database_constants_1 = require("../../../shared/database/database.constants");
|
|
20
|
-
const { annotationTasks, connectors, models, projects, releaseLineEvents, releaseLines,
|
|
20
|
+
const { annotationTasks, connectors, models, projects, prompts, releaseLineEvents, releaseLines, releaseVersions } = db_1.schema;
|
|
21
21
|
let ReleaseLineRepository = class ReleaseLineRepository {
|
|
22
22
|
constructor(db) {
|
|
23
23
|
this.db = db;
|
|
@@ -74,6 +74,15 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
74
74
|
.orderBy((0, drizzle_orm_1.desc)(releaseLineEvents.createdAt));
|
|
75
75
|
return this.hydrateEvents(rows);
|
|
76
76
|
}
|
|
77
|
+
async findEventById(projectId, releaseLineId, eventId) {
|
|
78
|
+
const rows = await this.db
|
|
79
|
+
.select()
|
|
80
|
+
.from(releaseLineEvents)
|
|
81
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.id, eventId)))
|
|
82
|
+
.limit(1);
|
|
83
|
+
const hydrated = await this.hydrateEvents(rows);
|
|
84
|
+
return hydrated[0] ?? null;
|
|
85
|
+
}
|
|
77
86
|
async record(snapshot) {
|
|
78
87
|
const releaseLineId = await this.db.transaction(async (tx) => {
|
|
79
88
|
const now = snapshot.updatedAt ?? new Date();
|
|
@@ -99,6 +108,19 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
99
108
|
})
|
|
100
109
|
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, line.id), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'production'), (0, drizzle_orm_1.eq)(releaseLineEvents.status, 'running')));
|
|
101
110
|
}
|
|
111
|
+
if (snapshot.laneType === 'production' && productionOperationReleasesCanarySlot(snapshot.operation)) {
|
|
112
|
+
await tx
|
|
113
|
+
.update(releaseLineEvents)
|
|
114
|
+
.set({
|
|
115
|
+
status: 'cancelled',
|
|
116
|
+
terminalReason: 'cancelled',
|
|
117
|
+
finishedAt: now,
|
|
118
|
+
controlState: null,
|
|
119
|
+
controlStatePayload: null,
|
|
120
|
+
updatedAt: now,
|
|
121
|
+
})
|
|
122
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, line.id), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'canary'), (0, drizzle_orm_1.sql) `${releaseLineEvents.status} IN ('running', 'stopped')`));
|
|
123
|
+
}
|
|
102
124
|
if (snapshot.laneType === 'production' && snapshot.operation === 'force_stop') {
|
|
103
125
|
await tx
|
|
104
126
|
.update(releaseLineEvents)
|
|
@@ -129,8 +151,8 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
129
151
|
.set({ status: 'completed', terminalReason: 'replaced', finishedAt: now, updatedAt: now })
|
|
130
152
|
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, line.id), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'canary'), (0, drizzle_orm_1.sql) `${releaseLineEvents.status} IN ('running', 'stopped')`));
|
|
131
153
|
}
|
|
132
|
-
const
|
|
133
|
-
const eventValues = await this.buildEventInsert(snapshot, line.id, supersedesEventId,
|
|
154
|
+
const releaseVersion = await this.resolveReleaseVersion(tx, line.id, snapshot, now);
|
|
155
|
+
const eventValues = await this.buildEventInsert(snapshot, line.id, supersedesEventId, releaseVersion?.id ?? null, now);
|
|
134
156
|
const inserted = await tx.insert(releaseLineEvents).values(eventValues).returning();
|
|
135
157
|
const event = inserted[0];
|
|
136
158
|
if (!event)
|
|
@@ -149,24 +171,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
149
171
|
if (!line || !canary || (canary.status !== 'running' && canary.status !== 'stopped'))
|
|
150
172
|
return null;
|
|
151
173
|
if (canary.status === 'running' && canary.trafficMode === 'split' && trafficRatio >= 1) {
|
|
152
|
-
|
|
153
|
-
...eventDtoToSnapshot(line, canary),
|
|
154
|
-
laneType: 'production',
|
|
155
|
-
operation: 'promote_canary',
|
|
156
|
-
status: 'running',
|
|
157
|
-
terminalReason: null,
|
|
158
|
-
sourceEventId: canary.id,
|
|
159
|
-
trafficMode: null,
|
|
160
|
-
trafficRatio: null,
|
|
161
|
-
submitReason: promotionSubmitReason(line),
|
|
162
|
-
createdBy: actorUserId,
|
|
163
|
-
createdAt: new Date(),
|
|
164
|
-
updatedAt: new Date(),
|
|
165
|
-
legacySource: null,
|
|
166
|
-
legacySourceId: null,
|
|
167
|
-
}));
|
|
168
|
-
await this.completeCanaryEvent(canary.id, 'promoted');
|
|
169
|
-
return this.findById(projectId, promoted.id);
|
|
174
|
+
return this.promoteCanaryEvent(line, canary, actorUserId);
|
|
170
175
|
}
|
|
171
176
|
await this.record(resetRuntimeStats({
|
|
172
177
|
...eventDtoToSnapshot(line, canary),
|
|
@@ -182,6 +187,474 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
182
187
|
}));
|
|
183
188
|
return this.findById(projectId, releaseLineId);
|
|
184
189
|
}
|
|
190
|
+
async promoteActiveCanary(projectId, releaseLineId, actorUserId) {
|
|
191
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
192
|
+
const canary = line?.activeCanaryEvent;
|
|
193
|
+
if (!line || !canary || canary.status !== 'running')
|
|
194
|
+
return null;
|
|
195
|
+
return this.promoteCanaryEvent(line, canary, actorUserId);
|
|
196
|
+
}
|
|
197
|
+
async stopLine(projectId, releaseLineId, reason, actorUserId) {
|
|
198
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
199
|
+
if (!line || line.status === 'archived')
|
|
200
|
+
return null;
|
|
201
|
+
const runningProduction = line.currentProductionEvent?.status === 'running' ? line.currentProductionEvent : null;
|
|
202
|
+
const runningCanary = line.activeCanaryEvent?.status === 'running' ? line.activeCanaryEvent : null;
|
|
203
|
+
if (!runningProduction && !runningCanary)
|
|
204
|
+
return null;
|
|
205
|
+
const now = new Date();
|
|
206
|
+
if (runningProduction) {
|
|
207
|
+
const stopped = await this.record(resetRuntimeStats({
|
|
208
|
+
...eventDtoToSnapshot(line, runningProduction),
|
|
209
|
+
operation: 'force_stop',
|
|
210
|
+
status: 'stopped',
|
|
211
|
+
terminalReason: 'force_stopped',
|
|
212
|
+
submitReason: reason,
|
|
213
|
+
createdBy: actorUserId,
|
|
214
|
+
createdAt: now,
|
|
215
|
+
updatedAt: now,
|
|
216
|
+
legacySource: null,
|
|
217
|
+
legacySourceId: null,
|
|
218
|
+
}));
|
|
219
|
+
await this.clearPromptProductionVersion(runningProduction.promptId);
|
|
220
|
+
return this.findById(projectId, stopped.id);
|
|
221
|
+
}
|
|
222
|
+
if (!runningCanary)
|
|
223
|
+
return null;
|
|
224
|
+
const stopped = await this.record(resetRuntimeStats({
|
|
225
|
+
...eventDtoToSnapshot(line, runningCanary),
|
|
226
|
+
operation: 'stop_lane',
|
|
227
|
+
status: 'stopped',
|
|
228
|
+
terminalReason: null,
|
|
229
|
+
supersedesEventId: runningCanary.id,
|
|
230
|
+
submitReason: reason,
|
|
231
|
+
createdBy: actorUserId,
|
|
232
|
+
createdAt: now,
|
|
233
|
+
updatedAt: now,
|
|
234
|
+
legacySource: null,
|
|
235
|
+
legacySourceId: null,
|
|
236
|
+
}));
|
|
237
|
+
return this.findById(projectId, stopped.id);
|
|
238
|
+
}
|
|
239
|
+
async startLine(projectId, releaseLineId, reason, actorUserId) {
|
|
240
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
241
|
+
if (!line || line.status !== 'stopped')
|
|
242
|
+
return null;
|
|
243
|
+
const resumableEvents = findResumableEvents(line);
|
|
244
|
+
if (resumableEvents.length === 0)
|
|
245
|
+
return null;
|
|
246
|
+
const now = new Date();
|
|
247
|
+
let started = null;
|
|
248
|
+
for (const resumable of resumableEvents) {
|
|
249
|
+
started = await this.record(resetRuntimeStats({
|
|
250
|
+
...eventDtoToSnapshot(line, resumable),
|
|
251
|
+
operation: 'resume_lane',
|
|
252
|
+
status: 'running',
|
|
253
|
+
terminalReason: null,
|
|
254
|
+
supersedesEventId: resumable.id,
|
|
255
|
+
submitReason: reason ?? 'start release line',
|
|
256
|
+
controlState: null,
|
|
257
|
+
controlStatePayload: null,
|
|
258
|
+
startedAt: now,
|
|
259
|
+
finishedAt: null,
|
|
260
|
+
createdBy: actorUserId,
|
|
261
|
+
createdAt: now,
|
|
262
|
+
updatedAt: now,
|
|
263
|
+
legacySource: null,
|
|
264
|
+
legacySourceId: null,
|
|
265
|
+
}));
|
|
266
|
+
if (resumable.laneType === 'production') {
|
|
267
|
+
await this.setPromptProductionVersion(projectId, resumable.promptId, resumable.promptVersionId);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return this.findById(projectId, started?.id ?? releaseLineId);
|
|
271
|
+
}
|
|
272
|
+
async archiveLine(projectId, releaseLineId, reason, actorUserId) {
|
|
273
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
274
|
+
if (!line)
|
|
275
|
+
return null;
|
|
276
|
+
if (line.status === 'archived')
|
|
277
|
+
return line;
|
|
278
|
+
const hasRunningLane = line.currentProductionEvent?.status === 'running' || line.activeCanaryEvent?.status === 'running';
|
|
279
|
+
if (hasRunningLane)
|
|
280
|
+
return null;
|
|
281
|
+
const slotEvents = findVisibleSlotEvents(line);
|
|
282
|
+
const fallbackEvent = line.latestEvent ?? line.activeCanaryEvent ?? line.currentProductionEvent ?? null;
|
|
283
|
+
const archiveTargets = slotEvents.length > 0 || !fallbackEvent ? slotEvents : [fallbackEvent];
|
|
284
|
+
const now = new Date();
|
|
285
|
+
await this.db.transaction(async (tx) => {
|
|
286
|
+
let currentProductionEventId = line.currentProductionEventId;
|
|
287
|
+
let activeCanaryEventId = line.activeCanaryEventId;
|
|
288
|
+
for (const target of archiveTargets) {
|
|
289
|
+
const eventValues = await this.buildEventInsert({
|
|
290
|
+
...eventDtoToSnapshot(line, target),
|
|
291
|
+
operation: 'archive_line',
|
|
292
|
+
status: 'archived',
|
|
293
|
+
terminalReason: 'archived',
|
|
294
|
+
supersedesEventId: target.id,
|
|
295
|
+
submitReason: reason ?? 'archive release line',
|
|
296
|
+
controlState: null,
|
|
297
|
+
controlStatePayload: null,
|
|
298
|
+
finishedAt: now,
|
|
299
|
+
createdBy: actorUserId,
|
|
300
|
+
createdAt: now,
|
|
301
|
+
updatedAt: now,
|
|
302
|
+
legacySource: null,
|
|
303
|
+
legacySourceId: null,
|
|
304
|
+
}, line.id, target.id, target.releaseVersionId, now);
|
|
305
|
+
const inserted = await tx.insert(releaseLineEvents).values(eventValues).returning({ id: releaseLineEvents.id });
|
|
306
|
+
const archivedEventId = inserted[0]?.id ?? null;
|
|
307
|
+
if (target.laneType === 'production')
|
|
308
|
+
currentProductionEventId = archivedEventId;
|
|
309
|
+
if (target.laneType === 'canary')
|
|
310
|
+
activeCanaryEventId = archivedEventId;
|
|
311
|
+
}
|
|
312
|
+
await tx
|
|
313
|
+
.update(releaseLines)
|
|
314
|
+
.set({ status: 'archived', currentProductionEventId, activeCanaryEventId, archivedAt: now, updatedAt: now })
|
|
315
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLines.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId)));
|
|
316
|
+
});
|
|
317
|
+
return this.findById(projectId, releaseLineId);
|
|
318
|
+
}
|
|
319
|
+
async unarchiveLine(projectId, releaseLineId, reason, actorUserId) {
|
|
320
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
321
|
+
if (!line || line.status !== 'archived')
|
|
322
|
+
return null;
|
|
323
|
+
const slotEvents = findArchivedSlotEvents(line);
|
|
324
|
+
const fallbackEvent = line.latestEvent ?? line.activeCanaryEvent ?? line.currentProductionEvent ?? null;
|
|
325
|
+
const restoreTargets = slotEvents.length > 0 || !fallbackEvent ? slotEvents : [fallbackEvent];
|
|
326
|
+
const now = new Date();
|
|
327
|
+
if (restoreTargets.length === 0) {
|
|
328
|
+
await this.db
|
|
329
|
+
.update(releaseLines)
|
|
330
|
+
.set({ status: 'stopped', archivedAt: null, updatedAt: now })
|
|
331
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLines.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId)));
|
|
332
|
+
return this.findById(projectId, releaseLineId);
|
|
333
|
+
}
|
|
334
|
+
let restored = null;
|
|
335
|
+
for (const target of restoreTargets) {
|
|
336
|
+
restored = await this.record(resetRuntimeStats({
|
|
337
|
+
...eventDtoToSnapshot(line, target),
|
|
338
|
+
operation: 'unarchive_line',
|
|
339
|
+
status: 'stopped',
|
|
340
|
+
terminalReason: null,
|
|
341
|
+
supersedesEventId: target.id,
|
|
342
|
+
submitReason: reason ?? 'unarchive release line',
|
|
343
|
+
controlState: null,
|
|
344
|
+
controlStatePayload: null,
|
|
345
|
+
finishedAt: now,
|
|
346
|
+
createdBy: actorUserId,
|
|
347
|
+
createdAt: now,
|
|
348
|
+
updatedAt: now,
|
|
349
|
+
legacySource: null,
|
|
350
|
+
legacySourceId: null,
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
return this.findById(projectId, restored?.id ?? releaseLineId);
|
|
354
|
+
}
|
|
355
|
+
async restoreHistoryToLane(projectId, releaseLineId, sourceEventId, targetLaneType, reason, actorUserId) {
|
|
356
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
357
|
+
if (!line || line.status === 'archived')
|
|
358
|
+
return null;
|
|
359
|
+
const source = await this.findEventById(projectId, releaseLineId, sourceEventId);
|
|
360
|
+
if (!source || !source.promptVersionId || !source.modelId)
|
|
361
|
+
return null;
|
|
362
|
+
const currentCanary = line.activeCanaryEvent;
|
|
363
|
+
const status = line.status === 'running' ? 'running' : 'stopped';
|
|
364
|
+
const now = new Date();
|
|
365
|
+
const restored = await this.record(resetRuntimeStats({
|
|
366
|
+
...eventDtoToSnapshot(line, source),
|
|
367
|
+
laneType: targetLaneType,
|
|
368
|
+
operation: targetLaneType === 'production' ? 'restore_to_production' : 'restore_to_canary',
|
|
369
|
+
status,
|
|
370
|
+
terminalReason: null,
|
|
371
|
+
releaseVersionId: null,
|
|
372
|
+
sourceEventId: source.id,
|
|
373
|
+
supersedesEventId: targetLaneType === 'production' ? line.currentProductionEvent?.id : line.activeCanaryEvent?.id,
|
|
374
|
+
rollbackTargetEventId: targetLaneType === 'production' ? source.id : null,
|
|
375
|
+
trafficMode: targetLaneType === 'canary' ? (source.trafficMode ?? currentCanary?.trafficMode ?? 'split') : null,
|
|
376
|
+
trafficRatio: targetLaneType === 'canary' ? (source.trafficRatio ?? currentCanary?.trafficRatio ?? 0.1) : null,
|
|
377
|
+
submitReason: reason ??
|
|
378
|
+
(targetLaneType === 'production'
|
|
379
|
+
? 'restore history to production slot'
|
|
380
|
+
: 'restore history to canary slot'),
|
|
381
|
+
controlState: null,
|
|
382
|
+
controlStatePayload: null,
|
|
383
|
+
startedAt: status === 'running' ? now : null,
|
|
384
|
+
finishedAt: null,
|
|
385
|
+
createdBy: actorUserId,
|
|
386
|
+
createdAt: now,
|
|
387
|
+
updatedAt: now,
|
|
388
|
+
legacySource: null,
|
|
389
|
+
legacySourceId: null,
|
|
390
|
+
}));
|
|
391
|
+
if (targetLaneType === 'production' && status === 'running') {
|
|
392
|
+
await this.setPromptProductionVersion(projectId, source.promptId, source.promptVersionId);
|
|
393
|
+
}
|
|
394
|
+
return this.findById(projectId, restored.id);
|
|
395
|
+
}
|
|
396
|
+
async listDeletionImpact(projectId, releaseLineId) {
|
|
397
|
+
const lineRows = await this.db
|
|
398
|
+
.select({ id: releaseLines.id, name: releaseLines.name })
|
|
399
|
+
.from(releaseLines)
|
|
400
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLines.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId)))
|
|
401
|
+
.limit(1);
|
|
402
|
+
const line = lineRows[0];
|
|
403
|
+
if (!line)
|
|
404
|
+
return null;
|
|
405
|
+
const eventRows = await this.db
|
|
406
|
+
.select({
|
|
407
|
+
id: releaseLineEvents.id,
|
|
408
|
+
operation: releaseLineEvents.operation,
|
|
409
|
+
laneType: releaseLineEvents.laneType,
|
|
410
|
+
status: releaseLineEvents.status,
|
|
411
|
+
createdAt: releaseLineEvents.createdAt,
|
|
412
|
+
})
|
|
413
|
+
.from(releaseLineEvents)
|
|
414
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId)))
|
|
415
|
+
.orderBy((0, drizzle_orm_1.desc)(releaseLineEvents.createdAt));
|
|
416
|
+
const versionRows = await this.db
|
|
417
|
+
.select()
|
|
418
|
+
.from(releaseVersions)
|
|
419
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseVersions.projectId, projectId), (0, drizzle_orm_1.eq)(releaseVersions.releaseLineId, releaseLineId)))
|
|
420
|
+
.orderBy(releaseVersions.targetProductionVersionNumber, releaseVersions.kind, releaseVersions.candidateNumber);
|
|
421
|
+
const taskRows = await this.db
|
|
422
|
+
.select({
|
|
423
|
+
id: annotationTasks.id,
|
|
424
|
+
name: annotationTasks.name,
|
|
425
|
+
status: annotationTasks.status,
|
|
426
|
+
scope: annotationTasks.scope,
|
|
427
|
+
createdAt: annotationTasks.createdAt,
|
|
428
|
+
})
|
|
429
|
+
.from(annotationTasks)
|
|
430
|
+
.where((0, drizzle_orm_1.sql) `
|
|
431
|
+
${annotationTasks.releaseLineEventId} IN (
|
|
432
|
+
SELECT id FROM ph_releases.release_line_events
|
|
433
|
+
WHERE project_id = ${projectId}::uuid AND release_line_id = ${releaseLineId}::uuid
|
|
434
|
+
)
|
|
435
|
+
OR ${annotationTasks.releaseVersionId} IN (
|
|
436
|
+
SELECT id FROM ph_releases.release_versions
|
|
437
|
+
WHERE project_id = ${projectId}::uuid AND release_line_id = ${releaseLineId}::uuid
|
|
438
|
+
)
|
|
439
|
+
`)
|
|
440
|
+
.orderBy((0, drizzle_orm_1.desc)(annotationTasks.createdAt));
|
|
441
|
+
const runResultRows = await this.db.execute((0, drizzle_orm_1.sql) `
|
|
442
|
+
SELECT COUNT(*)::int AS count
|
|
443
|
+
FROM ph_runs.run_results rr
|
|
444
|
+
WHERE rr.project_id = ${projectId}::uuid
|
|
445
|
+
AND rr.source = 'release'
|
|
446
|
+
AND (
|
|
447
|
+
rr.source_id IN (
|
|
448
|
+
SELECT id FROM ph_releases.release_line_events
|
|
449
|
+
WHERE project_id = ${projectId}::uuid AND release_line_id = ${releaseLineId}::uuid
|
|
450
|
+
)
|
|
451
|
+
OR rr.release_version_id IN (
|
|
452
|
+
SELECT id FROM ph_releases.release_versions
|
|
453
|
+
WHERE project_id = ${projectId}::uuid AND release_line_id = ${releaseLineId}::uuid
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
`);
|
|
457
|
+
const runResults = Number(unwrapRows(runResultRows)[0]?.['count'] ?? 0);
|
|
458
|
+
return {
|
|
459
|
+
line,
|
|
460
|
+
events: eventRows.map((row) => ({
|
|
461
|
+
id: row.id,
|
|
462
|
+
name: row.operation,
|
|
463
|
+
status: row.status,
|
|
464
|
+
detail: row.laneType,
|
|
465
|
+
createdAt: row.createdAt,
|
|
466
|
+
})),
|
|
467
|
+
versions: versionRows.map((row) => ({
|
|
468
|
+
id: row.id,
|
|
469
|
+
name: formatReleaseVersionLabel(row),
|
|
470
|
+
status: row.kind,
|
|
471
|
+
detail: row.promptName,
|
|
472
|
+
createdAt: row.createdAt,
|
|
473
|
+
})),
|
|
474
|
+
annotationTasks: taskRows.map((row) => ({
|
|
475
|
+
id: row.id,
|
|
476
|
+
name: row.name,
|
|
477
|
+
status: row.status,
|
|
478
|
+
detail: row.scope,
|
|
479
|
+
createdAt: row.createdAt,
|
|
480
|
+
})),
|
|
481
|
+
runResults,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Force-stop every running lane of a release line and drop it out of the runner's runnable set,
|
|
486
|
+
* committed in its OWN transaction ahead of hardDeleteLine. Once the line's slot events are no longer
|
|
487
|
+
* 'running', the next runner scan's findRunnableLine returns null and stops dispatching — a best-effort
|
|
488
|
+
* barrier before the physical delete. A residual LLM job already enqueued before this call may still
|
|
489
|
+
* write a run result; hardDeleteLine's cascade removes those, and any landing after deletion either fail
|
|
490
|
+
* the run_results.release_version_id FK or leave a harmless orphan (permanent delete is a confirmed
|
|
491
|
+
* dangerous action). Archived lines stay archived.
|
|
492
|
+
*/
|
|
493
|
+
async forceStopRunningLanesForDelete(projectId, releaseLineId) {
|
|
494
|
+
await this.db.transaction(async (tx) => {
|
|
495
|
+
const now = new Date();
|
|
496
|
+
await tx
|
|
497
|
+
.update(releaseLineEvents)
|
|
498
|
+
.set({
|
|
499
|
+
status: 'stopped',
|
|
500
|
+
terminalReason: 'force_stopped',
|
|
501
|
+
finishedAt: now,
|
|
502
|
+
controlState: null,
|
|
503
|
+
controlStatePayload: null,
|
|
504
|
+
updatedAt: now,
|
|
505
|
+
})
|
|
506
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.status, 'running')));
|
|
507
|
+
await tx
|
|
508
|
+
.update(releaseLines)
|
|
509
|
+
.set({
|
|
510
|
+
status: (0, drizzle_orm_1.sql) `CASE WHEN ${releaseLines.status} = 'archived' THEN 'archived' ELSE 'stopped' END`,
|
|
511
|
+
updatedAt: now,
|
|
512
|
+
})
|
|
513
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLines.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId)));
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async hardDeleteLine(projectId, releaseLineId) {
|
|
517
|
+
return this.db.transaction(async (tx) => {
|
|
518
|
+
await tx.execute((0, drizzle_orm_1.sql) `
|
|
519
|
+
WITH target_events AS (
|
|
520
|
+
SELECT id, prompt_id, prompt_version_id
|
|
521
|
+
FROM ph_releases.release_line_events
|
|
522
|
+
WHERE project_id = ${projectId}::uuid
|
|
523
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
524
|
+
),
|
|
525
|
+
target_versions AS (
|
|
526
|
+
SELECT id
|
|
527
|
+
FROM ph_releases.release_versions
|
|
528
|
+
WHERE project_id = ${projectId}::uuid
|
|
529
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
530
|
+
),
|
|
531
|
+
target_tasks AS (
|
|
532
|
+
SELECT id
|
|
533
|
+
FROM ph_releases.annotation_tasks
|
|
534
|
+
WHERE release_line_event_id IN (SELECT id FROM target_events)
|
|
535
|
+
OR release_version_id IN (SELECT id FROM target_versions)
|
|
536
|
+
),
|
|
537
|
+
target_run_results AS (
|
|
538
|
+
SELECT rr.id, rr.created_at
|
|
539
|
+
FROM ph_runs.run_results rr
|
|
540
|
+
WHERE rr.project_id = ${projectId}::uuid
|
|
541
|
+
AND rr.source = 'release'
|
|
542
|
+
AND (
|
|
543
|
+
rr.source_id IN (SELECT id FROM target_events)
|
|
544
|
+
OR rr.release_version_id IN (SELECT id FROM target_versions)
|
|
545
|
+
)
|
|
546
|
+
)
|
|
547
|
+
DELETE FROM ph_runs.annotations annotation
|
|
548
|
+
WHERE annotation.task_id IN (SELECT id FROM target_tasks)
|
|
549
|
+
OR EXISTS (
|
|
550
|
+
SELECT 1
|
|
551
|
+
FROM target_run_results rr
|
|
552
|
+
WHERE annotation.run_result_id = rr.id
|
|
553
|
+
AND annotation.run_result_created_at = rr.created_at
|
|
554
|
+
)
|
|
555
|
+
`);
|
|
556
|
+
await tx.execute((0, drizzle_orm_1.sql) `
|
|
557
|
+
WITH target_events AS (
|
|
558
|
+
SELECT id
|
|
559
|
+
FROM ph_releases.release_line_events
|
|
560
|
+
WHERE project_id = ${projectId}::uuid
|
|
561
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
562
|
+
),
|
|
563
|
+
target_versions AS (
|
|
564
|
+
SELECT id
|
|
565
|
+
FROM ph_releases.release_versions
|
|
566
|
+
WHERE project_id = ${projectId}::uuid
|
|
567
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
568
|
+
),
|
|
569
|
+
target_run_results AS (
|
|
570
|
+
SELECT rr.id, rr.created_at
|
|
571
|
+
FROM ph_runs.run_results rr
|
|
572
|
+
WHERE rr.project_id = ${projectId}::uuid
|
|
573
|
+
AND rr.source = 'release'
|
|
574
|
+
AND (
|
|
575
|
+
rr.source_id IN (SELECT id FROM target_events)
|
|
576
|
+
OR rr.release_version_id IN (SELECT id FROM target_versions)
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
DELETE FROM ph_runs.run_results rr
|
|
580
|
+
USING target_run_results target
|
|
581
|
+
WHERE rr.id = target.id
|
|
582
|
+
AND rr.created_at = target.created_at
|
|
583
|
+
`);
|
|
584
|
+
await tx.execute((0, drizzle_orm_1.sql) `
|
|
585
|
+
WITH target_events AS (
|
|
586
|
+
SELECT id, prompt_id, prompt_version_id
|
|
587
|
+
FROM ph_releases.release_line_events
|
|
588
|
+
WHERE project_id = ${projectId}::uuid
|
|
589
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
590
|
+
),
|
|
591
|
+
target_versions AS (
|
|
592
|
+
SELECT id
|
|
593
|
+
FROM ph_releases.release_versions
|
|
594
|
+
WHERE project_id = ${projectId}::uuid
|
|
595
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
596
|
+
)
|
|
597
|
+
UPDATE ph_assets.prompts prompt
|
|
598
|
+
SET current_online_version_id = NULL,
|
|
599
|
+
updated_at = NOW()
|
|
600
|
+
WHERE prompt.project_id = ${projectId}::uuid
|
|
601
|
+
AND prompt.current_online_version_id IN (
|
|
602
|
+
SELECT prompt_version_id
|
|
603
|
+
FROM target_events
|
|
604
|
+
WHERE prompt_id = prompt.id
|
|
605
|
+
AND prompt_version_id IS NOT NULL
|
|
606
|
+
)
|
|
607
|
+
`);
|
|
608
|
+
await tx.execute((0, drizzle_orm_1.sql) `
|
|
609
|
+
WITH target_events AS (
|
|
610
|
+
SELECT id
|
|
611
|
+
FROM ph_releases.release_line_events
|
|
612
|
+
WHERE project_id = ${projectId}::uuid
|
|
613
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
614
|
+
),
|
|
615
|
+
target_versions AS (
|
|
616
|
+
SELECT id
|
|
617
|
+
FROM ph_releases.release_versions
|
|
618
|
+
WHERE project_id = ${projectId}::uuid
|
|
619
|
+
AND release_line_id = ${releaseLineId}::uuid
|
|
620
|
+
)
|
|
621
|
+
DELETE FROM ph_releases.annotation_tasks task
|
|
622
|
+
WHERE task.release_line_event_id IN (SELECT id FROM target_events)
|
|
623
|
+
OR task.release_version_id IN (SELECT id FROM target_versions)
|
|
624
|
+
`);
|
|
625
|
+
await tx
|
|
626
|
+
.delete(releaseLineEvents)
|
|
627
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId)));
|
|
628
|
+
await tx
|
|
629
|
+
.delete(releaseVersions)
|
|
630
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseVersions.projectId, projectId), (0, drizzle_orm_1.eq)(releaseVersions.releaseLineId, releaseLineId)));
|
|
631
|
+
const deleted = await tx
|
|
632
|
+
.delete(releaseLines)
|
|
633
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLines.projectId, projectId), (0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId)))
|
|
634
|
+
.returning({ id: releaseLines.id });
|
|
635
|
+
return deleted.length;
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async promoteCanaryEvent(line, canary, actorUserId) {
|
|
639
|
+
const promoted = await this.record(resetRuntimeStats({
|
|
640
|
+
...eventDtoToSnapshot(line, canary),
|
|
641
|
+
laneType: 'production',
|
|
642
|
+
operation: 'promote_canary',
|
|
643
|
+
status: 'running',
|
|
644
|
+
terminalReason: null,
|
|
645
|
+
sourceEventId: canary.id,
|
|
646
|
+
trafficMode: null,
|
|
647
|
+
trafficRatio: null,
|
|
648
|
+
submitReason: promotionSubmitReason(line),
|
|
649
|
+
createdBy: actorUserId,
|
|
650
|
+
createdAt: new Date(),
|
|
651
|
+
updatedAt: new Date(),
|
|
652
|
+
legacySource: null,
|
|
653
|
+
legacySourceId: null,
|
|
654
|
+
}));
|
|
655
|
+
await this.completeCanaryEvent(canary.id, 'promoted');
|
|
656
|
+
return this.findById(line.projectId, promoted.id);
|
|
657
|
+
}
|
|
185
658
|
async updateActiveLaneRunConfig(projectId, releaseLineId, input, actorUserId) {
|
|
186
659
|
const line = await this.findById(projectId, releaseLineId);
|
|
187
660
|
if (!line)
|
|
@@ -197,15 +670,20 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
197
670
|
: null;
|
|
198
671
|
if (input.modelId && input.modelId !== event.modelId && (!nextModel || !nextModel.isActive))
|
|
199
672
|
return null;
|
|
673
|
+
const nextRunConfig = inheritCanaryStopConditions(input.laneType, event.runConfig, input.runConfig);
|
|
674
|
+
const releaseVersionId = nextModel || hasTemperatureChanged(event.runConfig, nextRunConfig) ? null : event.releaseVersionId;
|
|
200
675
|
const updated = await this.record(resetRuntimeStats({
|
|
201
676
|
...eventDtoToSnapshot(line, event),
|
|
677
|
+
releaseVersionId,
|
|
202
678
|
operation: 'config_changed',
|
|
203
679
|
terminalReason: null,
|
|
204
680
|
supersedesEventId: event.id,
|
|
205
681
|
modelId: nextModel?.id ?? event.modelId,
|
|
206
682
|
modelName: nextModel?.name ?? event.modelName,
|
|
207
683
|
modelProvider: nextModel?.providerType ?? event.modelProvider,
|
|
208
|
-
runConfig:
|
|
684
|
+
runConfig: nextRunConfig,
|
|
685
|
+
recordMode: input.recordMode ?? event.recordMode,
|
|
686
|
+
recordCategories: normalizeRecordCategoriesForMode(input.recordMode ?? event.recordMode, input.recordCategories ?? event.recordCategories),
|
|
209
687
|
submitReason: nextModel && input.laneType === 'production'
|
|
210
688
|
? '正式发布模型与运行配置变更'
|
|
211
689
|
: nextModel
|
|
@@ -221,6 +699,88 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
221
699
|
}));
|
|
222
700
|
return this.findById(projectId, updated.id);
|
|
223
701
|
}
|
|
702
|
+
async updateActiveLaneOutputRoute(projectId, releaseLineId, input, actorUserId) {
|
|
703
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
704
|
+
if (!line)
|
|
705
|
+
return null;
|
|
706
|
+
const event = input.laneType === 'production' ? line.currentProductionEvent : line.activeCanaryEvent;
|
|
707
|
+
if (!event)
|
|
708
|
+
return null;
|
|
709
|
+
if (event.status !== 'running' && event.status !== 'stopped')
|
|
710
|
+
return null;
|
|
711
|
+
const now = new Date();
|
|
712
|
+
const outputMappingChanged = JSON.stringify(event.outputMapping ?? []) !== JSON.stringify(input.outputMapping);
|
|
713
|
+
const updated = await this.record(resetRuntimeStats({
|
|
714
|
+
...eventDtoToSnapshot(line, event),
|
|
715
|
+
releaseVersionId: outputMappingChanged ? null : event.releaseVersionId,
|
|
716
|
+
operation: 'config_changed',
|
|
717
|
+
terminalReason: null,
|
|
718
|
+
supersedesEventId: event.id,
|
|
719
|
+
outputConnectorIds: input.outputConnectorIds,
|
|
720
|
+
outputMapping: input.outputMapping,
|
|
721
|
+
submitReason: input.laneType === 'production' ? '正式发布输出路由变更' : '灰度发布输出路由变更',
|
|
722
|
+
createdBy: actorUserId,
|
|
723
|
+
createdAt: now,
|
|
724
|
+
updatedAt: now,
|
|
725
|
+
legacySource: null,
|
|
726
|
+
legacySourceId: null,
|
|
727
|
+
}));
|
|
728
|
+
return this.findById(projectId, updated.id);
|
|
729
|
+
}
|
|
730
|
+
async updateActiveLaneInputRoute(projectId, releaseLineId, input, actorUserId) {
|
|
731
|
+
const line = await this.findById(projectId, releaseLineId);
|
|
732
|
+
if (!line)
|
|
733
|
+
return null;
|
|
734
|
+
const event = input.laneType === 'production' ? line.currentProductionEvent : line.activeCanaryEvent;
|
|
735
|
+
if (!event)
|
|
736
|
+
return null;
|
|
737
|
+
if (event.status !== 'running' && event.status !== 'stopped')
|
|
738
|
+
return null;
|
|
739
|
+
const now = new Date();
|
|
740
|
+
const variableMappingChanged = JSON.stringify(event.variableMapping ?? {}) !== JSON.stringify(input.variableMapping);
|
|
741
|
+
const externalIdFieldChanged = (event.externalIdField ?? '') !== input.externalIdField;
|
|
742
|
+
const updated = await this.record(resetRuntimeStats({
|
|
743
|
+
...eventDtoToSnapshot(line, event),
|
|
744
|
+
releaseVersionId: variableMappingChanged || externalIdFieldChanged ? null : event.releaseVersionId,
|
|
745
|
+
operation: 'config_changed',
|
|
746
|
+
terminalReason: null,
|
|
747
|
+
supersedesEventId: event.id,
|
|
748
|
+
variableMapping: input.variableMapping,
|
|
749
|
+
filterRules: input.filterRules,
|
|
750
|
+
externalIdField: input.externalIdField,
|
|
751
|
+
submitReason: input.laneType === 'production' ? '正式发布输入路由变更' : '灰度发布输入路由变更',
|
|
752
|
+
createdBy: actorUserId,
|
|
753
|
+
createdAt: now,
|
|
754
|
+
updatedAt: now,
|
|
755
|
+
legacySource: null,
|
|
756
|
+
legacySourceId: null,
|
|
757
|
+
}));
|
|
758
|
+
return this.findById(projectId, updated.id);
|
|
759
|
+
}
|
|
760
|
+
async listConnectorsForProject(projectId, ids) {
|
|
761
|
+
if (ids.length === 0)
|
|
762
|
+
return [];
|
|
763
|
+
return this.db
|
|
764
|
+
.select({ id: connectors.id, name: connectors.name, type: connectors.type, direction: connectors.direction })
|
|
765
|
+
.from(connectors)
|
|
766
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(connectors.projectId, projectId), (0, drizzle_orm_1.inArray)(connectors.id, ids), (0, drizzle_orm_1.isNull)(connectors.deletedAt)));
|
|
767
|
+
}
|
|
768
|
+
async clearPromptProductionVersion(promptId) {
|
|
769
|
+
if (!promptId)
|
|
770
|
+
return;
|
|
771
|
+
await this.db
|
|
772
|
+
.update(prompts)
|
|
773
|
+
.set({ currentOnlineVersionId: null, updatedAt: new Date() })
|
|
774
|
+
.where((0, drizzle_orm_1.eq)(prompts.id, promptId));
|
|
775
|
+
}
|
|
776
|
+
async setPromptProductionVersion(projectId, promptId, promptVersionId) {
|
|
777
|
+
if (!promptId || !promptVersionId)
|
|
778
|
+
return;
|
|
779
|
+
await this.db
|
|
780
|
+
.update(prompts)
|
|
781
|
+
.set({ currentOnlineVersionId: promptVersionId, updatedAt: new Date() })
|
|
782
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(prompts.projectId, projectId), (0, drizzle_orm_1.eq)(prompts.id, promptId)));
|
|
783
|
+
}
|
|
224
784
|
async findModelSnapshotForProject(projectId, modelId) {
|
|
225
785
|
const rows = await this.db
|
|
226
786
|
.select({
|
|
@@ -275,11 +835,11 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
275
835
|
.from(releaseLineEvents)
|
|
276
836
|
.where((0, drizzle_orm_1.inArray)(releaseLineEvents.id, Array.from(eventIds)))
|
|
277
837
|
: [];
|
|
278
|
-
const
|
|
838
|
+
const versionRows = await this.db
|
|
279
839
|
.select()
|
|
280
|
-
.from(
|
|
281
|
-
.where((0, drizzle_orm_1.inArray)(
|
|
282
|
-
.orderBy(
|
|
840
|
+
.from(releaseVersions)
|
|
841
|
+
.where((0, drizzle_orm_1.inArray)(releaseVersions.releaseLineId, lines.map((line) => line.id)))
|
|
842
|
+
.orderBy(releaseVersions.targetProductionVersionNumber, releaseVersions.kind, releaseVersions.candidateNumber);
|
|
283
843
|
const hydratedEvents = await this.hydrateEvents([...latestRows, ...explicitEvents]);
|
|
284
844
|
const eventById = new Map(hydratedEvents.map((event) => [event.id, event]));
|
|
285
845
|
const latestByLine = new Map();
|
|
@@ -287,11 +847,11 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
287
847
|
if (!latestByLine.has(event.releaseLineId))
|
|
288
848
|
latestByLine.set(event.releaseLineId, event);
|
|
289
849
|
}
|
|
290
|
-
const
|
|
291
|
-
for (const
|
|
292
|
-
const list =
|
|
293
|
-
list.push(
|
|
294
|
-
|
|
850
|
+
const versionsByLine = new Map();
|
|
851
|
+
for (const version of versionRows) {
|
|
852
|
+
const list = versionsByLine.get(version.releaseLineId) ?? [];
|
|
853
|
+
list.push(toReleaseVersionDto(version));
|
|
854
|
+
versionsByLine.set(version.releaseLineId, list);
|
|
295
855
|
}
|
|
296
856
|
return lines.map((line) => {
|
|
297
857
|
const currentProductionEvent = line.currentProductionEventId
|
|
@@ -315,7 +875,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
315
875
|
activeCanaryEventId: line.activeCanaryEventId,
|
|
316
876
|
currentProductionEvent,
|
|
317
877
|
activeCanaryEvent,
|
|
318
|
-
|
|
878
|
+
versions: versionsByLine.get(line.id) ?? [],
|
|
319
879
|
outputConnectors: mergeOutputConnectors(currentProductionEvent, activeCanaryEvent),
|
|
320
880
|
latestEvent: latestByLine.get(line.id) ?? null,
|
|
321
881
|
createdBy: line.createdBy,
|
|
@@ -331,15 +891,15 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
331
891
|
const outputIds = new Set();
|
|
332
892
|
const sourceEventIds = new Set();
|
|
333
893
|
const eventIds = new Set();
|
|
334
|
-
const
|
|
894
|
+
const versionIds = new Set();
|
|
335
895
|
for (const row of rows) {
|
|
336
896
|
eventIds.add(row.id);
|
|
337
897
|
for (const id of row.outputConnectorIds ?? [])
|
|
338
898
|
outputIds.add(id);
|
|
339
899
|
if (row.sourceEventId)
|
|
340
900
|
sourceEventIds.add(row.sourceEventId);
|
|
341
|
-
if (row.
|
|
342
|
-
|
|
901
|
+
if (row.releaseVersionId)
|
|
902
|
+
versionIds.add(row.releaseVersionId);
|
|
343
903
|
}
|
|
344
904
|
const outputMap = new Map();
|
|
345
905
|
if (outputIds.size > 0) {
|
|
@@ -367,14 +927,14 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
367
927
|
});
|
|
368
928
|
}
|
|
369
929
|
}
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
372
|
-
const
|
|
930
|
+
const versionMap = new Map();
|
|
931
|
+
if (versionIds.size > 0) {
|
|
932
|
+
const versionRows = await this.db
|
|
373
933
|
.select()
|
|
374
|
-
.from(
|
|
375
|
-
.where((0, drizzle_orm_1.inArray)(
|
|
376
|
-
for (const
|
|
377
|
-
|
|
934
|
+
.from(releaseVersions)
|
|
935
|
+
.where((0, drizzle_orm_1.inArray)(releaseVersions.id, Array.from(versionIds)));
|
|
936
|
+
for (const version of versionRows)
|
|
937
|
+
versionMap.set(version.id, version);
|
|
378
938
|
}
|
|
379
939
|
const annotationTaskMap = new Map();
|
|
380
940
|
if (eventIds.size > 0) {
|
|
@@ -389,7 +949,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
389
949
|
}
|
|
390
950
|
}
|
|
391
951
|
return rows.map((row) => {
|
|
392
|
-
const
|
|
952
|
+
const version = row.releaseVersionId ? (versionMap.get(row.releaseVersionId) ?? null) : null;
|
|
393
953
|
const outputSnapshots = asArrayOfRecords(row.outputConnectorSnapshots);
|
|
394
954
|
const outputConnectors = (row.outputConnectorIds ?? []).map((id) => {
|
|
395
955
|
const fromJoin = outputMap.get(id);
|
|
@@ -407,9 +967,12 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
407
967
|
id: row.id,
|
|
408
968
|
projectId: row.projectId,
|
|
409
969
|
releaseLineId: row.releaseLineId,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
970
|
+
releaseVersionId: row.releaseVersionId,
|
|
971
|
+
releaseVersionKind: version?.kind ?? null,
|
|
972
|
+
releaseVersionLabel: version ? formatReleaseVersionLabel(version) : null,
|
|
973
|
+
releaseVersionProductionNumber: version?.productionVersionNumber ?? null,
|
|
974
|
+
releaseVersionTargetProductionNumber: version?.targetProductionVersionNumber ?? null,
|
|
975
|
+
releaseVersionCandidateNumber: version?.candidateNumber ?? null,
|
|
413
976
|
annotationTaskId: annotationTaskMap.get(row.id) ?? null,
|
|
414
977
|
laneType: row.laneType,
|
|
415
978
|
operation: row.operation,
|
|
@@ -447,6 +1010,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
447
1010
|
outputMapping: row.outputMapping,
|
|
448
1011
|
filterRules: row.filterRules,
|
|
449
1012
|
recordMode: row.recordMode,
|
|
1013
|
+
recordCategories: row.recordCategories ?? [],
|
|
450
1014
|
externalIdField: row.externalIdField,
|
|
451
1015
|
retentionDays: row.retentionDays,
|
|
452
1016
|
sourceExperimentId: row.sourceExperimentId,
|
|
@@ -502,7 +1066,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
502
1066
|
inputConnectorName: snapshot.inputConnectorName,
|
|
503
1067
|
inputConnectorType: snapshot.inputConnectorType,
|
|
504
1068
|
inputConnectorSnapshot: connectorSnapshot(snapshot.inputConnectorId, snapshot.inputConnectorName, snapshot.inputConnectorType),
|
|
505
|
-
status: snapshot.
|
|
1069
|
+
status: snapshot.status === 'running' ? 'running' : 'stopped',
|
|
506
1070
|
createdBy: snapshot.createdBy,
|
|
507
1071
|
createdAt: snapshot.createdAt ?? now,
|
|
508
1072
|
updatedAt: now,
|
|
@@ -513,41 +1077,48 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
513
1077
|
throw new Error('release_lines insert returned no row');
|
|
514
1078
|
return line;
|
|
515
1079
|
}
|
|
516
|
-
async
|
|
1080
|
+
async resolveReleaseVersion(tx, releaseLineId, snapshot, now) {
|
|
517
1081
|
if (!snapshot.promptVersionId || !snapshot.modelId)
|
|
518
1082
|
return null;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
.update(releaseVariants)
|
|
528
|
-
.set({
|
|
529
|
-
promptName: snapshot.promptName,
|
|
530
|
-
promptVersionNumber: snapshot.promptVersionNumber ?? null,
|
|
531
|
-
promptSnapshot: snapshot.promptSnapshot,
|
|
532
|
-
promptVersionSnapshot: snapshot.promptVersionSnapshot,
|
|
533
|
-
modelSnapshot: modelSnapshot(snapshot.modelId, snapshot.modelName, snapshot.modelProvider),
|
|
534
|
-
updatedAt: now,
|
|
535
|
-
})
|
|
536
|
-
.where((0, drizzle_orm_1.eq)(releaseVariants.id, found.id))
|
|
537
|
-
.returning();
|
|
538
|
-
return updated[0] ?? found;
|
|
1083
|
+
if (shouldReuseReleaseVersion(snapshot) && snapshot.releaseVersionId) {
|
|
1084
|
+
const existing = await tx
|
|
1085
|
+
.select()
|
|
1086
|
+
.from(releaseVersions)
|
|
1087
|
+
.where((0, drizzle_orm_1.eq)(releaseVersions.id, snapshot.releaseVersionId))
|
|
1088
|
+
.limit(1);
|
|
1089
|
+
if (existing[0])
|
|
1090
|
+
return existing[0];
|
|
539
1091
|
}
|
|
540
|
-
const
|
|
541
|
-
.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1092
|
+
const promotedFromReleaseVersionId = snapshot.operation === 'promote_canary'
|
|
1093
|
+
? (snapshot.releaseVersionId ?? (await this.findEventReleaseVersionId(tx, snapshot.sourceEventId)))
|
|
1094
|
+
: null;
|
|
1095
|
+
return this.createReleaseVersion(tx, releaseLineId, snapshot, promotedFromReleaseVersionId, now);
|
|
1096
|
+
}
|
|
1097
|
+
async createReleaseVersion(tx, releaseLineId, snapshot, promotedFromReleaseVersionId, now) {
|
|
1098
|
+
if (!snapshot.promptVersionId || !snapshot.modelId)
|
|
1099
|
+
return null;
|
|
1100
|
+
const kind = snapshot.laneType === 'canary' ? 'candidate' : 'production';
|
|
1101
|
+
const maxProductionRows = await tx
|
|
1102
|
+
.select({
|
|
1103
|
+
maxProductionVersionNumber: (0, drizzle_orm_1.sql) `MAX(${releaseVersions.productionVersionNumber})::int`,
|
|
1104
|
+
})
|
|
1105
|
+
.from(releaseVersions)
|
|
1106
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseVersions.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseVersions.kind, 'production')));
|
|
1107
|
+
const nextProductionVersionNumber = Number(maxProductionRows[0]?.maxProductionVersionNumber ?? 0) + 1;
|
|
1108
|
+
const targetProductionVersionNumber = kind === 'production'
|
|
1109
|
+
? nextProductionVersionNumber
|
|
1110
|
+
: Number(maxProductionRows[0]?.maxProductionVersionNumber ?? 0) + 1;
|
|
1111
|
+
const candidateNumber = kind === 'candidate' ? await this.nextCandidateNumber(tx, releaseLineId, targetProductionVersionNumber) : null;
|
|
545
1112
|
const inserted = await tx
|
|
546
|
-
.insert(
|
|
1113
|
+
.insert(releaseVersions)
|
|
547
1114
|
.values({
|
|
548
1115
|
projectId: snapshot.projectId,
|
|
549
1116
|
releaseLineId,
|
|
550
|
-
|
|
1117
|
+
kind,
|
|
1118
|
+
productionVersionNumber: kind === 'production' ? nextProductionVersionNumber : null,
|
|
1119
|
+
targetProductionVersionNumber,
|
|
1120
|
+
candidateNumber,
|
|
1121
|
+
promotedFromReleaseVersionId,
|
|
551
1122
|
promptId: snapshot.promptId ?? null,
|
|
552
1123
|
promptName: snapshot.promptName,
|
|
553
1124
|
promptVersionId: snapshot.promptVersionId,
|
|
@@ -563,6 +1134,24 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
563
1134
|
.returning();
|
|
564
1135
|
return inserted[0] ?? null;
|
|
565
1136
|
}
|
|
1137
|
+
async nextCandidateNumber(tx, releaseLineId, targetProductionVersionNumber) {
|
|
1138
|
+
const maxCandidateRows = await tx
|
|
1139
|
+
.select()
|
|
1140
|
+
.from(releaseVersions)
|
|
1141
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseVersions.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseVersions.kind, 'candidate'), (0, drizzle_orm_1.eq)(releaseVersions.targetProductionVersionNumber, targetProductionVersionNumber)));
|
|
1142
|
+
const maxCandidateNumber = maxCandidateRows.reduce((max, row) => Math.max(max, row.candidateNumber ?? 0), 0);
|
|
1143
|
+
return maxCandidateNumber + 1;
|
|
1144
|
+
}
|
|
1145
|
+
async findEventReleaseVersionId(tx, eventId) {
|
|
1146
|
+
if (!eventId)
|
|
1147
|
+
return null;
|
|
1148
|
+
const rows = await tx
|
|
1149
|
+
.select({ releaseVersionId: releaseLineEvents.releaseVersionId })
|
|
1150
|
+
.from(releaseLineEvents)
|
|
1151
|
+
.where((0, drizzle_orm_1.eq)(releaseLineEvents.id, eventId))
|
|
1152
|
+
.limit(1);
|
|
1153
|
+
return rows[0]?.releaseVersionId ?? null;
|
|
1154
|
+
}
|
|
566
1155
|
async findExistingLegacyEvent(tx, snapshot) {
|
|
567
1156
|
if (!snapshot.legacySource || !snapshot.legacySourceId)
|
|
568
1157
|
return null;
|
|
@@ -573,7 +1162,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
573
1162
|
.limit(1);
|
|
574
1163
|
return rows[0] ?? null;
|
|
575
1164
|
}
|
|
576
|
-
async buildEventInsert(snapshot, releaseLineId, supersedesEventId,
|
|
1165
|
+
async buildEventInsert(snapshot, releaseLineId, supersedesEventId, releaseVersionId, now) {
|
|
577
1166
|
const outputSnapshots = await this.loadOutputConnectorSnapshots(snapshot.outputConnectorIds);
|
|
578
1167
|
return {
|
|
579
1168
|
projectId: snapshot.projectId,
|
|
@@ -587,7 +1176,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
587
1176
|
rollbackTargetEventId: snapshot.rollbackTargetEventId ?? null,
|
|
588
1177
|
legacySource: snapshot.legacySource ?? null,
|
|
589
1178
|
legacySourceId: snapshot.legacySourceId ?? null,
|
|
590
|
-
|
|
1179
|
+
releaseVersionId,
|
|
591
1180
|
promptId: snapshot.promptId ?? null,
|
|
592
1181
|
promptName: snapshot.promptName,
|
|
593
1182
|
promptVersionId: snapshot.promptVersionId ?? null,
|
|
@@ -607,6 +1196,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
607
1196
|
outputMapping: (snapshot.outputMapping ?? []),
|
|
608
1197
|
filterRules: snapshot.filterRules,
|
|
609
1198
|
recordMode: snapshot.recordMode,
|
|
1199
|
+
recordCategories: normalizeRecordCategoriesForMode(snapshot.recordMode, snapshot.recordCategories),
|
|
610
1200
|
externalIdField: snapshot.externalIdField ?? null,
|
|
611
1201
|
retentionDays: snapshot.retentionDays ?? null,
|
|
612
1202
|
sourceExperimentId: snapshot.sourceExperimentId ?? null,
|
|
@@ -627,21 +1217,34 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
627
1217
|
};
|
|
628
1218
|
}
|
|
629
1219
|
async updateLinePointers(tx, releaseLineId, event, now) {
|
|
1220
|
+
const lineRows = await tx
|
|
1221
|
+
.select({ status: releaseLines.status, archivedAt: releaseLines.archivedAt })
|
|
1222
|
+
.from(releaseLines)
|
|
1223
|
+
.where((0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId))
|
|
1224
|
+
.limit(1);
|
|
630
1225
|
const productionRows = await tx
|
|
631
|
-
.select({ id: releaseLineEvents.id })
|
|
1226
|
+
.select({ id: releaseLineEvents.id, status: releaseLineEvents.status })
|
|
632
1227
|
.from(releaseLineEvents)
|
|
633
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'production'), (0, drizzle_orm_1.
|
|
1228
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'production'), (0, drizzle_orm_1.sql) `${releaseLineEvents.status} IN ('running', 'stopped', 'archived')`))
|
|
634
1229
|
.orderBy((0, drizzle_orm_1.desc)(releaseLineEvents.createdAt))
|
|
635
1230
|
.limit(1);
|
|
636
1231
|
const canaryRows = await tx
|
|
637
|
-
.select({ id: releaseLineEvents.id })
|
|
1232
|
+
.select({ id: releaseLineEvents.id, status: releaseLineEvents.status })
|
|
638
1233
|
.from(releaseLineEvents)
|
|
639
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'canary'), (0, drizzle_orm_1.sql) `${releaseLineEvents.status} IN ('running', 'stopped')`))
|
|
1234
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(releaseLineEvents.releaseLineId, releaseLineId), (0, drizzle_orm_1.eq)(releaseLineEvents.laneType, 'canary'), (0, drizzle_orm_1.sql) `${releaseLineEvents.status} IN ('running', 'stopped', 'archived')`))
|
|
640
1235
|
.orderBy((0, drizzle_orm_1.desc)(releaseLineEvents.createdAt))
|
|
641
1236
|
.limit(1);
|
|
642
1237
|
const currentProductionEventId = productionRows[0]?.id ?? null;
|
|
643
1238
|
const activeCanaryEventId = canaryRows[0]?.id ?? null;
|
|
644
|
-
|
|
1239
|
+
// Mirror release-runner.repository.refreshLinePointersByEvent: an archived line is
|
|
1240
|
+
// non-runnable and must NEVER be silently resurrected by a new mirror event. The only
|
|
1241
|
+
// legitimate departure from 'archived' is an explicit unarchive (operation 'unarchive_line',
|
|
1242
|
+
// which records a 'stopped' event); every other event leaves status/archivedAt untouched.
|
|
1243
|
+
const lineCurrentlyArchived = (lineRows[0]?.status ?? null) === 'archived' && event.operation !== 'unarchive_line';
|
|
1244
|
+
const status = lineCurrentlyArchived
|
|
1245
|
+
? 'archived'
|
|
1246
|
+
: lineStatus(productionRows[0]?.status ?? null, canaryRows[0]?.status ?? null);
|
|
1247
|
+
const archivedAt = lineCurrentlyArchived ? (lineRows[0]?.archivedAt ?? null) : null;
|
|
645
1248
|
await tx
|
|
646
1249
|
.update(releaseLines)
|
|
647
1250
|
.set({
|
|
@@ -649,7 +1252,7 @@ let ReleaseLineRepository = class ReleaseLineRepository {
|
|
|
649
1252
|
activeCanaryEventId,
|
|
650
1253
|
status,
|
|
651
1254
|
updatedAt: now,
|
|
652
|
-
archivedAt
|
|
1255
|
+
archivedAt,
|
|
653
1256
|
})
|
|
654
1257
|
.where((0, drizzle_orm_1.eq)(releaseLines.id, releaseLineId));
|
|
655
1258
|
}
|
|
@@ -670,15 +1273,35 @@ exports.ReleaseLineRepository = ReleaseLineRepository = __decorate([
|
|
|
670
1273
|
__param(0, (0, common_1.Inject)(database_constants_1.DATABASE_CLIENT)),
|
|
671
1274
|
__metadata("design:paramtypes", [Object])
|
|
672
1275
|
], ReleaseLineRepository);
|
|
673
|
-
function lineStatus(
|
|
674
|
-
if (
|
|
675
|
-
return '
|
|
676
|
-
if (currentProductionEventId)
|
|
677
|
-
return 'production';
|
|
678
|
-
if (activeCanaryEventId)
|
|
679
|
-
return 'canary';
|
|
1276
|
+
function lineStatus(productionStatus, activeCanaryStatus) {
|
|
1277
|
+
if (productionStatus === 'running' || activeCanaryStatus === 'running')
|
|
1278
|
+
return 'running';
|
|
680
1279
|
return 'stopped';
|
|
681
1280
|
}
|
|
1281
|
+
function findResumableEvents(line) {
|
|
1282
|
+
const slotEvents = findVisibleSlotEvents(line).filter((event) => event.status === 'stopped');
|
|
1283
|
+
if (slotEvents.length > 0)
|
|
1284
|
+
return slotEvents;
|
|
1285
|
+
return line.latestEvent?.status === 'stopped' ? [line.latestEvent] : [];
|
|
1286
|
+
}
|
|
1287
|
+
function findVisibleSlotEvents(line) {
|
|
1288
|
+
const events = [line.currentProductionEvent, line.activeCanaryEvent].filter((event) => Boolean(event));
|
|
1289
|
+
return dedupeEvents(events);
|
|
1290
|
+
}
|
|
1291
|
+
function findArchivedSlotEvents(line) {
|
|
1292
|
+
return findVisibleSlotEvents(line).filter((event) => event.status === 'archived');
|
|
1293
|
+
}
|
|
1294
|
+
function dedupeEvents(events) {
|
|
1295
|
+
const seen = new Set();
|
|
1296
|
+
const result = [];
|
|
1297
|
+
for (const event of events) {
|
|
1298
|
+
if (seen.has(event.id))
|
|
1299
|
+
continue;
|
|
1300
|
+
seen.add(event.id);
|
|
1301
|
+
result.push(event);
|
|
1302
|
+
}
|
|
1303
|
+
return result;
|
|
1304
|
+
}
|
|
682
1305
|
function asRecord(value) {
|
|
683
1306
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
684
1307
|
}
|
|
@@ -687,6 +1310,13 @@ function asArrayOfRecords(value) {
|
|
|
687
1310
|
? value.filter((item) => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
|
|
688
1311
|
: [];
|
|
689
1312
|
}
|
|
1313
|
+
function inheritCanaryStopConditions(laneType, previousRunConfig, nextRunConfig) {
|
|
1314
|
+
const next = asRecord(nextRunConfig);
|
|
1315
|
+
if (laneType !== 'canary' || Object.prototype.hasOwnProperty.call(next, 'stopConditions'))
|
|
1316
|
+
return next;
|
|
1317
|
+
const previousStopConditions = asRecord(previousRunConfig)['stopConditions'];
|
|
1318
|
+
return previousStopConditions === undefined ? next : { ...next, stopConditions: previousStopConditions };
|
|
1319
|
+
}
|
|
690
1320
|
function stringFromSnapshot(snapshot, key) {
|
|
691
1321
|
const value = asRecord(snapshot)[key];
|
|
692
1322
|
return typeof value === 'string' ? value : null;
|
|
@@ -708,31 +1338,39 @@ function connectorSnapshot(id, name, type) {
|
|
|
708
1338
|
function modelSnapshot(id, name, providerType) {
|
|
709
1339
|
return { id, name, providerType };
|
|
710
1340
|
}
|
|
711
|
-
function
|
|
712
|
-
|
|
1341
|
+
function formatReleaseVersionLabel(version) {
|
|
1342
|
+
if (version.kind === 'production')
|
|
1343
|
+
return `v${version.productionVersionNumber ?? version.targetProductionVersionNumber}`;
|
|
1344
|
+
const baseProductionNumber = Math.max(0, version.targetProductionVersionNumber - 1);
|
|
1345
|
+
return `v${baseProductionNumber}.${version.candidateNumber ?? 0}`;
|
|
713
1346
|
}
|
|
714
|
-
function
|
|
715
|
-
const promptVersionNumber =
|
|
1347
|
+
function toReleaseVersionDto(version) {
|
|
1348
|
+
const promptVersionNumber = version.promptVersionNumber ?? numberFromSnapshot(version.promptVersionSnapshot, 'versionNumber');
|
|
716
1349
|
return {
|
|
717
|
-
id:
|
|
718
|
-
projectId:
|
|
719
|
-
releaseLineId:
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1350
|
+
id: version.id,
|
|
1351
|
+
projectId: version.projectId,
|
|
1352
|
+
releaseLineId: version.releaseLineId,
|
|
1353
|
+
kind: version.kind,
|
|
1354
|
+
productionVersionNumber: version.productionVersionNumber,
|
|
1355
|
+
targetProductionVersionNumber: version.targetProductionVersionNumber,
|
|
1356
|
+
candidateNumber: version.candidateNumber,
|
|
1357
|
+
promotedFromReleaseVersionId: version.promotedFromReleaseVersionId,
|
|
1358
|
+
label: formatReleaseVersionLabel(version),
|
|
1359
|
+
promptId: version.promptId,
|
|
1360
|
+
promptName: version.promptName,
|
|
1361
|
+
promptVersionId: version.promptVersionId,
|
|
725
1362
|
promptVersionNumber,
|
|
726
1363
|
promptVersionLabel: promptVersionNumber ? `v${promptVersionNumber}` : null,
|
|
727
|
-
promptSnapshot: asRecord(
|
|
728
|
-
promptVersionSnapshot: asRecord(
|
|
729
|
-
modelId:
|
|
730
|
-
modelName: stringFromSnapshot(
|
|
731
|
-
modelProvider: stringFromSnapshot(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1364
|
+
promptSnapshot: asRecord(version.promptSnapshot),
|
|
1365
|
+
promptVersionSnapshot: asRecord(version.promptVersionSnapshot),
|
|
1366
|
+
modelId: version.modelId,
|
|
1367
|
+
modelName: stringFromSnapshot(version.modelSnapshot, 'name'),
|
|
1368
|
+
modelProvider: stringFromSnapshot(version.modelSnapshot, 'providerType') ??
|
|
1369
|
+
stringFromSnapshot(version.modelSnapshot, 'provider'),
|
|
1370
|
+
modelSnapshot: asRecord(version.modelSnapshot),
|
|
1371
|
+
createdBy: version.createdBy,
|
|
1372
|
+
createdAt: version.createdAt.toISOString(),
|
|
1373
|
+
updatedAt: version.updatedAt.toISOString(),
|
|
736
1374
|
};
|
|
737
1375
|
}
|
|
738
1376
|
function mergeOutputConnectors(production, canary) {
|
|
@@ -743,6 +1381,20 @@ function mergeOutputConnectors(production, canary) {
|
|
|
743
1381
|
map.set(connector.id, connector);
|
|
744
1382
|
return [...map.values()];
|
|
745
1383
|
}
|
|
1384
|
+
function normalizeRecordCategoriesForMode(mode, categories) {
|
|
1385
|
+
if (mode === 'all')
|
|
1386
|
+
return [];
|
|
1387
|
+
const seen = new Set();
|
|
1388
|
+
const normalized = [];
|
|
1389
|
+
for (const category of categories ?? []) {
|
|
1390
|
+
const trimmed = category.trim();
|
|
1391
|
+
if (!trimmed || seen.has(trimmed))
|
|
1392
|
+
continue;
|
|
1393
|
+
seen.add(trimmed);
|
|
1394
|
+
normalized.push(trimmed);
|
|
1395
|
+
}
|
|
1396
|
+
return normalized;
|
|
1397
|
+
}
|
|
746
1398
|
function eventDtoToSnapshot(line, event) {
|
|
747
1399
|
return {
|
|
748
1400
|
projectId: event.projectId,
|
|
@@ -775,6 +1427,7 @@ function eventDtoToSnapshot(line, event) {
|
|
|
775
1427
|
outputMapping: event.outputMapping,
|
|
776
1428
|
filterRules: event.filterRules,
|
|
777
1429
|
recordMode: event.recordMode,
|
|
1430
|
+
recordCategories: event.recordCategories,
|
|
778
1431
|
externalIdField: event.externalIdField,
|
|
779
1432
|
retentionDays: event.retentionDays,
|
|
780
1433
|
sourceExperimentId: event.sourceExperimentId,
|
|
@@ -785,6 +1438,7 @@ function eventDtoToSnapshot(line, event) {
|
|
|
785
1438
|
totalFiltered: event.totalFiltered,
|
|
786
1439
|
totalCorrect: event.totalCorrect,
|
|
787
1440
|
totalErrors: event.totalErrors,
|
|
1441
|
+
releaseVersionId: event.releaseVersionId,
|
|
788
1442
|
controlState: event.controlState,
|
|
789
1443
|
controlStatePayload: event.controlStatePayload,
|
|
790
1444
|
startedAt: event.startedAt ? new Date(event.startedAt) : null,
|
|
@@ -824,6 +1478,30 @@ function resetRuntimeStats(snapshot) {
|
|
|
824
1478
|
totalErrors: 0,
|
|
825
1479
|
};
|
|
826
1480
|
}
|
|
1481
|
+
function shouldReuseReleaseVersion(snapshot) {
|
|
1482
|
+
if (!snapshot.releaseVersionId)
|
|
1483
|
+
return false;
|
|
1484
|
+
return ([
|
|
1485
|
+
'traffic_updated',
|
|
1486
|
+
'mode_updated',
|
|
1487
|
+
'stop_lane',
|
|
1488
|
+
'resume_lane',
|
|
1489
|
+
'cancel_canary',
|
|
1490
|
+
'force_stop',
|
|
1491
|
+
'archive_line',
|
|
1492
|
+
'unarchive_line',
|
|
1493
|
+
].includes(snapshot.operation) || snapshot.operation === 'config_changed');
|
|
1494
|
+
}
|
|
1495
|
+
function productionOperationReleasesCanarySlot(operation) {
|
|
1496
|
+
return operation === 'rollback' || operation === 'restore_to_production';
|
|
1497
|
+
}
|
|
1498
|
+
function hasTemperatureChanged(previousRunConfig, nextRunConfig) {
|
|
1499
|
+
const previous = asRecord(previousRunConfig)['temperature'];
|
|
1500
|
+
const next = asRecord(nextRunConfig)['temperature'];
|
|
1501
|
+
if (previous === undefined && next === undefined)
|
|
1502
|
+
return false;
|
|
1503
|
+
return Number(previous) !== Number(next);
|
|
1504
|
+
}
|
|
827
1505
|
function releaseLineIdentityCondition(identity) {
|
|
828
1506
|
if (identity.inputConnectorId)
|
|
829
1507
|
return (0, drizzle_orm_1.eq)(releaseLines.inputConnectorId, identity.inputConnectorId);
|
|
@@ -831,4 +1509,12 @@ function releaseLineIdentityCondition(identity) {
|
|
|
831
1509
|
return (0, drizzle_orm_1.and)((0, drizzle_orm_1.isNull)(releaseLines.inputConnectorId), (0, drizzle_orm_1.eq)(releaseLines.promptId, identity.promptId));
|
|
832
1510
|
return (0, drizzle_orm_1.isNull)(releaseLines.inputConnectorId);
|
|
833
1511
|
}
|
|
1512
|
+
function unwrapRows(result) {
|
|
1513
|
+
if (Array.isArray(result))
|
|
1514
|
+
return result;
|
|
1515
|
+
if (result && typeof result === 'object' && 'rows' in result) {
|
|
1516
|
+
return (result.rows ?? []);
|
|
1517
|
+
}
|
|
1518
|
+
return [];
|
|
1519
|
+
}
|
|
834
1520
|
//# sourceMappingURL=release-line.repository.js.map
|