stageflow 0.8.0 → 0.12.0
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/README.md +24 -14
- package/dist/agent/activity.d.ts +9 -0
- package/dist/agent/agentBackend.d.ts +32 -0
- package/dist/agent/agentBackend.js +38 -0
- package/dist/agent/claudeActivity.d.ts +15 -0
- package/dist/agent/claudeActivity.js +58 -0
- package/dist/agent/claudeAdapter.d.ts +5 -0
- package/dist/agent/claudeAdapter.js +301 -0
- package/dist/agent/claudeSession.d.ts +18 -0
- package/dist/agent/claudeSession.js +54 -0
- package/dist/agent/claudeTools.d.ts +89 -0
- package/dist/agent/claudeTools.js +135 -0
- package/dist/agent/fakeAgent.js +24 -4
- package/dist/agent/piAdapter.d.ts +6 -3
- package/dist/agent/piAdapter.js +129 -21
- package/dist/agent/port.d.ts +50 -1
- package/dist/agent/resolveAgentPort.d.ts +26 -0
- package/dist/agent/resolveAgentPort.js +39 -0
- package/dist/cli/runCommand.js +2 -2
- package/dist/cli/runsCommand.d.ts +22 -0
- package/dist/cli/runsCommand.js +773 -0
- package/dist/cli/validateOutput.d.ts +2 -2
- package/dist/cli/validateOutput.js +2 -2
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +43 -5
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +2 -1
- package/dist/config/createPipeline.js +34 -9
- package/dist/config/createStage.js +14 -9
- package/dist/config/loadPipeline.js +10 -1
- package/dist/config/loadStage.js +138 -2
- package/dist/config/loadStageflowManifest.js +14 -1
- package/dist/config/mergePipelineIncludes.d.ts +1 -0
- package/dist/config/mergePipelineIncludes.js +14 -1
- package/dist/config/normalizePipelineStageEntry.d.ts +7 -2
- package/dist/config/normalizePipelineStageEntry.js +49 -6
- package/dist/config/parseCompletionContract.d.ts +8 -0
- package/dist/config/parseCompletionContract.js +218 -0
- package/dist/config/parsePreEmitChecks.d.ts +3 -0
- package/dist/config/parsePreEmitChecks.js +65 -0
- package/dist/config/pipelineNeeds.d.ts +19 -0
- package/dist/config/pipelineNeeds.js +151 -0
- package/dist/config/pipelineStageKeys.js +16 -1
- package/dist/config/resolveForkEmitContext.d.ts +6 -2
- package/dist/config/resolveForkEmitContext.js +9 -2
- package/dist/config/resolvePipelineDag.d.ts +2 -1
- package/dist/config/resolvePipelineDag.js +182 -39
- package/dist/config/validateCatalog.d.ts +1 -1
- package/dist/config/validateCompletionContract.d.ts +4 -0
- package/dist/config/validateCompletionContract.js +52 -0
- package/dist/envelope/check.d.ts +1 -1
- package/dist/envelope/check.js +86 -13
- package/dist/envelope/cloneForks.d.ts +1 -1
- package/dist/envelope/cloneForks.js +24 -9
- package/dist/envelope/feedbackLoop.d.ts +4 -0
- package/dist/envelope/feedbackLoop.js +31 -0
- package/dist/envelope/payloadSchema.d.ts +7 -1
- package/dist/envelope/payloadSchema.js +170 -13
- package/dist/envelope/preEmitChecks.d.ts +29 -0
- package/dist/envelope/preEmitChecks.js +57 -0
- package/dist/hitl/qaTrail.d.ts +25 -0
- package/dist/hitl/qaTrail.js +51 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/catalogTools.js +4 -2
- package/dist/mcp/controlTools.js +72 -0
- package/dist/mcp/waitingGates.d.ts +2 -1
- package/dist/mcp/waitingGates.js +17 -1
- package/dist/package-meta.d.ts +1 -0
- package/dist/package-meta.js +1 -0
- package/dist/projection/projectRun.d.ts +4 -1
- package/dist/projection/projectRun.js +7 -0
- package/dist/prompt/feedbackLoopContext.d.ts +2 -0
- package/dist/prompt/feedbackLoopContext.js +17 -0
- package/dist/prompt/priorEnvelope.d.ts +2 -2
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/pipelineDagSnapshot.d.ts +1 -0
- package/dist/runstore/pipelineDagSnapshot.js +51 -11
- package/dist/runstore/port.d.ts +185 -3
- package/dist/runstore/port.js +42 -3
- package/dist/runstore/runProjection.d.ts +7 -3
- package/dist/runstore/runProjection.js +30 -15
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +25 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +626 -8
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +99 -0
- package/dist/runstore/stageInstanceId.d.ts +1 -1
- package/dist/runstore/stageInstanceId.js +3 -2
- package/dist/runstore/trackProjection.js +59 -27
- package/dist/runstore/verificationHistory.d.ts +19 -0
- package/dist/runstore/verificationHistory.js +30 -0
- package/dist/runtime/cloneSchedule.d.ts +40 -4
- package/dist/runtime/cloneSchedule.js +212 -28
- package/dist/runtime/completionCheckRunner.d.ts +137 -0
- package/dist/runtime/completionCheckRunner.js +365 -0
- package/dist/runtime/completionCheckoutBaseline.d.ts +3 -0
- package/dist/runtime/completionCheckoutBaseline.js +44 -0
- package/dist/runtime/envelopeRouting.d.ts +10 -3
- package/dist/runtime/envelopeRouting.js +146 -5
- package/dist/runtime/feedbackLoopCoordinator.d.ts +74 -0
- package/dist/runtime/feedbackLoopCoordinator.js +446 -0
- package/dist/runtime/feedbackLoopDecision.d.ts +44 -0
- package/dist/runtime/feedbackLoopDecision.js +207 -0
- package/dist/runtime/feedbackLoopRoute.d.ts +5 -0
- package/dist/runtime/feedbackLoopRoute.js +13 -0
- package/dist/runtime/feedbackLoopSchedule.d.ts +38 -0
- package/dist/runtime/feedbackLoopSchedule.js +72 -0
- package/dist/runtime/forkGeneration.d.ts +72 -0
- package/dist/runtime/forkGeneration.js +295 -0
- package/dist/runtime/gitCheckoutCapability.d.ts +7 -0
- package/dist/runtime/gitCheckoutCapability.js +97 -0
- package/dist/runtime/manualRecoveryState.d.ts +40 -0
- package/dist/runtime/manualRecoveryState.js +73 -0
- package/dist/runtime/pipelineScheduler.d.ts +13 -1
- package/dist/runtime/pipelineScheduler.js +328 -45
- package/dist/runtime/replayLifecycle.d.ts +107 -0
- package/dist/runtime/replayLifecycle.js +168 -0
- package/dist/runtime/runManager.d.ts +61 -1
- package/dist/runtime/runManager.js +501 -7
- package/dist/runtime/runRetryCoordinator.d.ts +8 -1
- package/dist/runtime/runRetryCoordinator.js +47 -10
- package/dist/runtime/stageAttemptBootstrap.d.ts +3 -1
- package/dist/runtime/stageAttemptBootstrap.js +79 -1
- package/dist/runtime/stageProcessLauncher.d.ts +1 -1
- package/dist/runtime/stageRecovery.js +1 -1
- package/dist/runtime/stageRunner.d.ts +4 -1
- package/dist/runtime/stageRunner.js +29 -3
- package/dist/runtime/stageWorker.js +69 -2
- package/dist/runtime/stageWorkerProtocol.d.ts +1 -1
- package/dist/runtime/verifiedStageExecution.d.ts +29 -0
- package/dist/runtime/verifiedStageExecution.js +119 -0
- package/dist/server/bootstrap.js +1 -0
- package/dist/server/http.js +114 -0
- package/dist/server/operatorResults.js +2 -0
- package/dist/tools/askOperator.d.ts +161 -5
- package/dist/tools/askOperator.js +58 -25
- package/dist/tools/emitStageEnvelope.d.ts +12 -12
- package/dist/tools/emitStageEnvelope.js +112 -24
- package/dist/types/completion.d.ts +43 -0
- package/dist/types/completion.js +1 -0
- package/dist/types/envelope.d.ts +18 -0
- package/dist/types/forkChoice.d.ts +9 -4
- package/dist/types/forkChoice.js +1 -1
- package/dist/types/pipeline.d.ts +39 -2
- package/dist/types/preEmitCheck.d.ts +18 -0
- package/dist/types/preEmitCheck.js +1 -0
- package/dist/types/stage.d.ts +13 -0
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/ui/assets/index-CXGz4nL7.js +118 -0
- package/dist/ui/assets/{index-C3N7MXAC.css → index-uVHiae8s.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/skills/stageflow/references/control-surface.md +4 -2
- package/skills/stageflow-author/references/catalog-mapping.md +26 -2
- package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
- package/skills/stageflow-run/SKILL.md +56 -25
- package/skills/stageflow-run/references/mcp-call.md +2 -2
- package/skills/stageflow-run/references/native-question-ui.md +78 -0
- package/dist/ui/assets/index-CSoGDA3A.js +0 -118
|
@@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS stage_executions (
|
|
|
59
59
|
stage_id TEXT NOT NULL,
|
|
60
60
|
attempt INTEGER NOT NULL,
|
|
61
61
|
status TEXT NOT NULL,
|
|
62
|
+
verification_outcome TEXT NOT NULL DEFAULT 'not_run',
|
|
62
63
|
started_at TEXT,
|
|
63
64
|
finished_at TEXT,
|
|
64
65
|
envelope_json TEXT,
|
|
@@ -69,6 +70,46 @@ CREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage
|
|
|
69
70
|
ON stage_executions (run_id, stage_id, attempt);
|
|
70
71
|
`);
|
|
71
72
|
}
|
|
73
|
+
function ensureStageExecutionVerificationOutcomeColumn(db) {
|
|
74
|
+
const cols = db
|
|
75
|
+
.prepare(`PRAGMA table_info(stage_executions)`)
|
|
76
|
+
.all();
|
|
77
|
+
if (!cols.some((c) => c.name === "verification_outcome")) {
|
|
78
|
+
db.exec(`ALTER TABLE stage_executions ADD COLUMN verification_outcome TEXT NOT NULL DEFAULT 'not_run'`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function backfillVerificationOutcomes(db) {
|
|
82
|
+
db.exec(`
|
|
83
|
+
UPDATE stage_executions
|
|
84
|
+
SET verification_outcome = CASE
|
|
85
|
+
WHEN status = 'succeeded'
|
|
86
|
+
AND EXISTS (
|
|
87
|
+
SELECT 1 FROM verification_check_results AS check_result
|
|
88
|
+
WHERE check_result.run_id = stage_executions.run_id
|
|
89
|
+
AND check_result.stage_id = stage_executions.stage_id
|
|
90
|
+
AND check_result.attempt = stage_executions.attempt
|
|
91
|
+
)
|
|
92
|
+
AND NOT EXISTS (
|
|
93
|
+
SELECT 1 FROM verification_check_results AS check_result
|
|
94
|
+
WHERE check_result.run_id = stage_executions.run_id
|
|
95
|
+
AND check_result.stage_id = stage_executions.stage_id
|
|
96
|
+
AND check_result.attempt = stage_executions.attempt
|
|
97
|
+
AND check_result.status != 'passed'
|
|
98
|
+
)
|
|
99
|
+
THEN 'passed'
|
|
100
|
+
WHEN status = 'failed'
|
|
101
|
+
AND EXISTS (
|
|
102
|
+
SELECT 1 FROM verification_check_results AS check_result
|
|
103
|
+
WHERE check_result.run_id = stage_executions.run_id
|
|
104
|
+
AND check_result.stage_id = stage_executions.stage_id
|
|
105
|
+
AND check_result.attempt = stage_executions.attempt
|
|
106
|
+
)
|
|
107
|
+
THEN 'failed'
|
|
108
|
+
ELSE verification_outcome
|
|
109
|
+
END
|
|
110
|
+
WHERE verification_outcome = 'not_run';
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
72
113
|
function ensureStageEventsAttemptColumn(db) {
|
|
73
114
|
const cols = db
|
|
74
115
|
.prepare(`PRAGMA table_info(stage_events)`)
|
|
@@ -81,12 +122,63 @@ CREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_attempt_at
|
|
|
81
122
|
`);
|
|
82
123
|
}
|
|
83
124
|
}
|
|
125
|
+
function ensureVerificationCheckResultsTable(db) {
|
|
126
|
+
db.exec(`
|
|
127
|
+
CREATE TABLE IF NOT EXISTS verification_check_results (
|
|
128
|
+
run_id TEXT NOT NULL,
|
|
129
|
+
stage_id TEXT NOT NULL,
|
|
130
|
+
attempt INTEGER NOT NULL,
|
|
131
|
+
check_id TEXT NOT NULL,
|
|
132
|
+
check_type TEXT NOT NULL,
|
|
133
|
+
status TEXT NOT NULL,
|
|
134
|
+
started_at TEXT,
|
|
135
|
+
finished_at TEXT,
|
|
136
|
+
evidence_json TEXT,
|
|
137
|
+
PRIMARY KEY (run_id, stage_id, attempt, check_id),
|
|
138
|
+
FOREIGN KEY (run_id, stage_id, attempt)
|
|
139
|
+
REFERENCES stage_executions(run_id, stage_id, attempt)
|
|
140
|
+
);
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_verification_check_results_execution
|
|
142
|
+
ON verification_check_results (run_id, stage_id, attempt, check_id);
|
|
143
|
+
`);
|
|
144
|
+
}
|
|
145
|
+
function ensureFeedbackReplayStagePassEnvelopeColumn(db) {
|
|
146
|
+
const cols = db
|
|
147
|
+
.prepare(`PRAGMA table_info(feedback_replay_stage_passes)`)
|
|
148
|
+
.all();
|
|
149
|
+
if (cols.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
if (!cols.some((c) => c.name === "emitted_envelope_json")) {
|
|
152
|
+
db.exec(`ALTER TABLE feedback_replay_stage_passes ADD COLUMN emitted_envelope_json TEXT`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function ensureFeedbackReplayStagePassSessionOriginColumn(db) {
|
|
156
|
+
const cols = db
|
|
157
|
+
.prepare(`PRAGMA table_info(feedback_replay_stage_passes)`)
|
|
158
|
+
.all();
|
|
159
|
+
if (cols.length === 0)
|
|
160
|
+
return;
|
|
161
|
+
if (!cols.some((c) => c.name === "session_origin_attempt")) {
|
|
162
|
+
db.exec(`ALTER TABLE feedback_replay_stage_passes ADD COLUMN session_origin_attempt INTEGER`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function ensureFeedbackLoopDeferredSendBackColumn(db) {
|
|
166
|
+
const cols = db
|
|
167
|
+
.prepare(`PRAGMA table_info(feedback_loops)`)
|
|
168
|
+
.all();
|
|
169
|
+
if (cols.length === 0)
|
|
170
|
+
return;
|
|
171
|
+
if (!cols.some((c) => c.name === "deferred_send_back_json")) {
|
|
172
|
+
db.exec(`ALTER TABLE feedback_loops ADD COLUMN deferred_send_back_json TEXT`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
84
175
|
function executionFromRow(row) {
|
|
85
176
|
return {
|
|
86
177
|
run_id: row.run_id,
|
|
87
178
|
stage_id: row.stage_id,
|
|
88
179
|
attempt: row.attempt,
|
|
89
180
|
status: row.status,
|
|
181
|
+
verification_outcome: row.verification_outcome ?? "not_run",
|
|
90
182
|
...(row.started_at != null ? { started_at: row.started_at } : {}),
|
|
91
183
|
...(row.finished_at != null ? { finished_at: row.finished_at } : {}),
|
|
92
184
|
envelope: row.envelope_json
|
|
@@ -94,6 +186,95 @@ function executionFromRow(row) {
|
|
|
94
186
|
: null,
|
|
95
187
|
};
|
|
96
188
|
}
|
|
189
|
+
function verificationCheckResultFromRow(row) {
|
|
190
|
+
return {
|
|
191
|
+
run_id: row.run_id,
|
|
192
|
+
stage_id: row.stage_id,
|
|
193
|
+
attempt: row.attempt,
|
|
194
|
+
check_id: row.check_id,
|
|
195
|
+
check_type: row.check_type,
|
|
196
|
+
status: row.status,
|
|
197
|
+
...(row.started_at != null ? { started_at: row.started_at } : {}),
|
|
198
|
+
...(row.finished_at != null ? { finished_at: row.finished_at } : {}),
|
|
199
|
+
...(row.evidence_json != null
|
|
200
|
+
? { evidence: JSON.parse(row.evidence_json) }
|
|
201
|
+
: {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function feedbackLoopFromRow(row) {
|
|
205
|
+
return {
|
|
206
|
+
run_id: row.run_id,
|
|
207
|
+
loop_id: row.loop_id,
|
|
208
|
+
source_stage_id: row.source_stage_id,
|
|
209
|
+
source_attempt: row.source_attempt,
|
|
210
|
+
policy: JSON.parse(row.policy_json),
|
|
211
|
+
state: row.state,
|
|
212
|
+
...(row.current_replay_id != null
|
|
213
|
+
? { current_replay_id: row.current_replay_id }
|
|
214
|
+
: {}),
|
|
215
|
+
...(row.current_replay_number != null
|
|
216
|
+
? { current_replay_number: row.current_replay_number }
|
|
217
|
+
: {}),
|
|
218
|
+
...(row.deferred_send_back_json != null
|
|
219
|
+
? {
|
|
220
|
+
deferred_send_back: JSON.parse(row.deferred_send_back_json),
|
|
221
|
+
}
|
|
222
|
+
: {}),
|
|
223
|
+
created_at: row.created_at,
|
|
224
|
+
updated_at: row.updated_at,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function feedbackReplayFromRow(row) {
|
|
228
|
+
return {
|
|
229
|
+
run_id: row.run_id,
|
|
230
|
+
replay_id: row.replay_id,
|
|
231
|
+
loop_id: row.loop_id,
|
|
232
|
+
source_stage_id: row.source_stage_id,
|
|
233
|
+
source_attempt: row.source_attempt,
|
|
234
|
+
target_stage_id: row.target_stage_id,
|
|
235
|
+
replay_number: row.replay_number,
|
|
236
|
+
max_replays: row.max_replays,
|
|
237
|
+
replay_session: row.replay_session,
|
|
238
|
+
route_stage_ids: JSON.parse(row.route_stage_ids_json),
|
|
239
|
+
feedback_envelope: JSON.parse(row.feedback_envelope_json),
|
|
240
|
+
status: row.status,
|
|
241
|
+
created_at: row.created_at,
|
|
242
|
+
updated_at: row.updated_at,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function feedbackReplayStagePassFromRow(row) {
|
|
246
|
+
return {
|
|
247
|
+
run_id: row.run_id,
|
|
248
|
+
replay_id: row.replay_id,
|
|
249
|
+
stage_id: row.stage_id,
|
|
250
|
+
stage_attempt: row.stage_attempt,
|
|
251
|
+
...(row.session_origin_attempt != null
|
|
252
|
+
? { session_origin_attempt: row.session_origin_attempt }
|
|
253
|
+
: {}),
|
|
254
|
+
session_mode: row.session_mode,
|
|
255
|
+
status: row.status,
|
|
256
|
+
...(row.started_at != null ? { started_at: row.started_at } : {}),
|
|
257
|
+
...(row.finished_at != null ? { finished_at: row.finished_at } : {}),
|
|
258
|
+
...(row.emitted_envelope_json != null
|
|
259
|
+
? {
|
|
260
|
+
emitted_envelope: JSON.parse(row.emitted_envelope_json),
|
|
261
|
+
}
|
|
262
|
+
: {}),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function forkGenerationFromRow(row) {
|
|
266
|
+
return {
|
|
267
|
+
run_id: row.run_id,
|
|
268
|
+
generation_id: row.generation_id,
|
|
269
|
+
...(row.replay_id != null ? { replay_id: row.replay_id } : {}),
|
|
270
|
+
fork_parent_stage_id: row.fork_parent_stage_id,
|
|
271
|
+
generation_number: row.generation_number,
|
|
272
|
+
clone_stage_ids: JSON.parse(row.clone_stage_ids_json),
|
|
273
|
+
status: row.status,
|
|
274
|
+
created_at: row.created_at,
|
|
275
|
+
updated_at: row.updated_at,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
97
278
|
export class SqliteRunStore {
|
|
98
279
|
storeRoot;
|
|
99
280
|
db;
|
|
@@ -110,7 +291,13 @@ export class SqliteRunStore {
|
|
|
110
291
|
ensurePipelineDagColumn(this.db);
|
|
111
292
|
ensureRunLocatorColumns(this.db);
|
|
112
293
|
ensureStageExecutionsTable(this.db);
|
|
294
|
+
ensureStageExecutionVerificationOutcomeColumn(this.db);
|
|
113
295
|
ensureStageEventsAttemptColumn(this.db);
|
|
296
|
+
ensureVerificationCheckResultsTable(this.db);
|
|
297
|
+
ensureFeedbackReplayStagePassEnvelopeColumn(this.db);
|
|
298
|
+
ensureFeedbackReplayStagePassSessionOriginColumn(this.db);
|
|
299
|
+
ensureFeedbackLoopDeferredSendBackColumn(this.db);
|
|
300
|
+
backfillVerificationOutcomes(this.db);
|
|
114
301
|
this.migratePromise = importDiskRunsIfEmpty(this.db, storeRoot).then(() => undefined);
|
|
115
302
|
}
|
|
116
303
|
/** Await one-shot disk import before catalog reads (create paths also wait). */
|
|
@@ -235,16 +422,17 @@ export class SqliteRunStore {
|
|
|
235
422
|
.get(runId, stageId);
|
|
236
423
|
const attempt = (maxRow?.max_attempt ?? 0) + 1;
|
|
237
424
|
this.db
|
|
238
|
-
.prepare(`INSERT INTO stage_executions (run_id, stage_id, attempt, status)
|
|
239
|
-
VALUES (@run_id, @stage_id, @attempt, @status)`)
|
|
425
|
+
.prepare(`INSERT INTO stage_executions (run_id, stage_id, attempt, status, verification_outcome)
|
|
426
|
+
VALUES (@run_id, @stage_id, @attempt, @status, @verification_outcome)`)
|
|
240
427
|
.run({
|
|
241
428
|
run_id: runId,
|
|
242
429
|
stage_id: stageId,
|
|
243
430
|
attempt,
|
|
244
431
|
status: "pending",
|
|
432
|
+
verification_outcome: "not_run",
|
|
245
433
|
});
|
|
246
434
|
const row = this.db
|
|
247
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, started_at, finished_at, envelope_json
|
|
435
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
248
436
|
FROM stage_executions
|
|
249
437
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
250
438
|
.get(runId, stageId, attempt);
|
|
@@ -254,7 +442,7 @@ export class SqliteRunStore {
|
|
|
254
442
|
async listStageExecutions(runId, stageId) {
|
|
255
443
|
await this.ready();
|
|
256
444
|
const rows = this.db
|
|
257
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, started_at, finished_at, envelope_json
|
|
445
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
258
446
|
FROM stage_executions
|
|
259
447
|
WHERE run_id = ? AND stage_id = ?
|
|
260
448
|
ORDER BY attempt ASC`)
|
|
@@ -264,7 +452,7 @@ export class SqliteRunStore {
|
|
|
264
452
|
async getLatestStageExecution(runId, stageId) {
|
|
265
453
|
await this.ready();
|
|
266
454
|
const row = this.db
|
|
267
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, started_at, finished_at, envelope_json
|
|
455
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
268
456
|
FROM stage_executions
|
|
269
457
|
WHERE run_id = ? AND stage_id = ?
|
|
270
458
|
ORDER BY attempt DESC
|
|
@@ -283,7 +471,7 @@ export class SqliteRunStore {
|
|
|
283
471
|
async getStageExecution(runId, stageId, attempt) {
|
|
284
472
|
await this.ready();
|
|
285
473
|
const row = this.db
|
|
286
|
-
.prepare(`SELECT run_id, stage_id, attempt, status, started_at, finished_at, envelope_json
|
|
474
|
+
.prepare(`SELECT run_id, stage_id, attempt, status, verification_outcome, started_at, finished_at, envelope_json
|
|
287
475
|
FROM stage_executions
|
|
288
476
|
WHERE run_id = ? AND stage_id = ? AND attempt = ?`)
|
|
289
477
|
.get(runId, stageId, attempt);
|
|
@@ -304,6 +492,10 @@ export class SqliteRunStore {
|
|
|
304
492
|
sets.push("status = @status");
|
|
305
493
|
params.status = patch.status;
|
|
306
494
|
}
|
|
495
|
+
if (patch.verification_outcome !== undefined) {
|
|
496
|
+
sets.push("verification_outcome = @verification_outcome");
|
|
497
|
+
params.verification_outcome = patch.verification_outcome;
|
|
498
|
+
}
|
|
307
499
|
if (patch.started_at !== undefined) {
|
|
308
500
|
sets.push("started_at = @started_at");
|
|
309
501
|
params.started_at = patch.started_at;
|
|
@@ -327,6 +519,50 @@ export class SqliteRunStore {
|
|
|
327
519
|
throw new Error(`Stage execution not found: ${runId}/${stageId} attempt ${attempt}`);
|
|
328
520
|
}
|
|
329
521
|
}
|
|
522
|
+
async upsertVerificationCheckResult(runId, stageId, result, options) {
|
|
523
|
+
await this.ready();
|
|
524
|
+
const attempt = options?.attempt ?? 1;
|
|
525
|
+
await this.getStageExecution(runId, stageId, attempt);
|
|
526
|
+
this.db
|
|
527
|
+
.prepare(`INSERT INTO verification_check_results
|
|
528
|
+
(run_id, stage_id, attempt, check_id, check_type, status, started_at, finished_at, evidence_json)
|
|
529
|
+
VALUES
|
|
530
|
+
(@run_id, @stage_id, @attempt, @check_id, @check_type, @status, @started_at, @finished_at, @evidence_json)
|
|
531
|
+
ON CONFLICT(run_id, stage_id, attempt, check_id) DO UPDATE SET
|
|
532
|
+
check_type = excluded.check_type,
|
|
533
|
+
status = excluded.status,
|
|
534
|
+
started_at = COALESCE(excluded.started_at, verification_check_results.started_at),
|
|
535
|
+
finished_at = COALESCE(excluded.finished_at, verification_check_results.finished_at),
|
|
536
|
+
evidence_json = COALESCE(excluded.evidence_json, verification_check_results.evidence_json)`)
|
|
537
|
+
.run({
|
|
538
|
+
run_id: runId,
|
|
539
|
+
stage_id: stageId,
|
|
540
|
+
attempt,
|
|
541
|
+
check_id: result.check_id,
|
|
542
|
+
check_type: result.check_type,
|
|
543
|
+
status: result.status,
|
|
544
|
+
started_at: result.started_at ?? null,
|
|
545
|
+
finished_at: result.finished_at ?? null,
|
|
546
|
+
evidence_json: result.evidence !== undefined ? JSON.stringify(result.evidence) : null,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
async listVerificationCheckResults(runId, stageId, attempt) {
|
|
550
|
+
await this.ready();
|
|
551
|
+
const rows = attempt === undefined
|
|
552
|
+
? this.db
|
|
553
|
+
.prepare(`SELECT run_id, stage_id, attempt, check_id, check_type, status, started_at, finished_at, evidence_json
|
|
554
|
+
FROM verification_check_results
|
|
555
|
+
WHERE run_id = ? AND stage_id = ?
|
|
556
|
+
ORDER BY attempt ASC, rowid ASC`)
|
|
557
|
+
.all(runId, stageId)
|
|
558
|
+
: this.db
|
|
559
|
+
.prepare(`SELECT run_id, stage_id, attempt, check_id, check_type, status, started_at, finished_at, evidence_json
|
|
560
|
+
FROM verification_check_results
|
|
561
|
+
WHERE run_id = ? AND stage_id = ? AND attempt = ?
|
|
562
|
+
ORDER BY rowid ASC`)
|
|
563
|
+
.all(runId, stageId, attempt);
|
|
564
|
+
return rows.map(verificationCheckResultFromRow);
|
|
565
|
+
}
|
|
330
566
|
async writeEnvelope(runId, stageId, envelope, options) {
|
|
331
567
|
const attempt = options?.attempt ?? 1;
|
|
332
568
|
await this.ensureAttemptWorkspace(runId, stageId, attempt);
|
|
@@ -451,7 +687,10 @@ export class SqliteRunStore {
|
|
|
451
687
|
for (const row of rows) {
|
|
452
688
|
const dagSnapshot = this.readPipelineDagSnapshotFromRow(row);
|
|
453
689
|
const stages = await this.loadStageSnapshots(row.run_id, dagSnapshot?.stage_ids);
|
|
454
|
-
|
|
690
|
+
const { active } = await this.loadFeedbackLoopHistory(row.run_id);
|
|
691
|
+
summaries.push(projectRunSummary(this.runMetaFromRow(row), stages, {
|
|
692
|
+
active_feedback_loop: active,
|
|
693
|
+
}));
|
|
455
694
|
}
|
|
456
695
|
return summaries;
|
|
457
696
|
}
|
|
@@ -460,7 +699,386 @@ export class SqliteRunStore {
|
|
|
460
699
|
const row = this.getRunRow(runId);
|
|
461
700
|
const dagSnapshot = this.readPipelineDagSnapshotFromRow(row);
|
|
462
701
|
const stages = await this.loadStageSnapshots(runId, dagSnapshot?.stage_ids);
|
|
463
|
-
|
|
702
|
+
const { history, active } = await this.loadFeedbackLoopHistory(runId);
|
|
703
|
+
return projectRunDetail(this.runMetaFromRow(row), stages, row.task_yaml, dagSnapshot, {
|
|
704
|
+
feedback_loops: history,
|
|
705
|
+
active_feedback_loop: active,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
async createFeedbackLoop(runId, input) {
|
|
709
|
+
await this.ready();
|
|
710
|
+
this.getRunRow(runId);
|
|
711
|
+
const now = new Date().toISOString();
|
|
712
|
+
const state = input.state ?? "active";
|
|
713
|
+
this.db
|
|
714
|
+
.prepare(`INSERT INTO feedback_loops
|
|
715
|
+
(run_id, loop_id, source_stage_id, source_attempt, policy_json, state,
|
|
716
|
+
current_replay_id, current_replay_number, created_at, updated_at)
|
|
717
|
+
VALUES
|
|
718
|
+
(@run_id, @loop_id, @source_stage_id, @source_attempt, @policy_json, @state,
|
|
719
|
+
NULL, NULL, @created_at, @updated_at)`)
|
|
720
|
+
.run({
|
|
721
|
+
run_id: runId,
|
|
722
|
+
loop_id: input.loop_id,
|
|
723
|
+
source_stage_id: input.source_stage_id,
|
|
724
|
+
source_attempt: input.source_attempt,
|
|
725
|
+
policy_json: JSON.stringify(input.policy),
|
|
726
|
+
state,
|
|
727
|
+
created_at: now,
|
|
728
|
+
updated_at: now,
|
|
729
|
+
});
|
|
730
|
+
return this.getFeedbackLoop(runId, input.loop_id);
|
|
731
|
+
}
|
|
732
|
+
async getFeedbackLoop(runId, loopId) {
|
|
733
|
+
await this.ready();
|
|
734
|
+
const row = this.db
|
|
735
|
+
.prepare(`SELECT run_id, loop_id, source_stage_id, source_attempt, policy_json, state,
|
|
736
|
+
current_replay_id, current_replay_number, deferred_send_back_json,
|
|
737
|
+
created_at, updated_at
|
|
738
|
+
FROM feedback_loops WHERE run_id = ? AND loop_id = ?`)
|
|
739
|
+
.get(runId, loopId);
|
|
740
|
+
if (!row) {
|
|
741
|
+
throw new Error(`Feedback loop not found: ${runId}/${loopId}`);
|
|
742
|
+
}
|
|
743
|
+
return feedbackLoopFromRow(row);
|
|
744
|
+
}
|
|
745
|
+
async listFeedbackLoops(runId) {
|
|
746
|
+
await this.ready();
|
|
747
|
+
this.getRunRow(runId);
|
|
748
|
+
const rows = this.db
|
|
749
|
+
.prepare(`SELECT run_id, loop_id, source_stage_id, source_attempt, policy_json, state,
|
|
750
|
+
current_replay_id, current_replay_number, deferred_send_back_json,
|
|
751
|
+
created_at, updated_at
|
|
752
|
+
FROM feedback_loops WHERE run_id = ? ORDER BY created_at ASC`)
|
|
753
|
+
.all(runId);
|
|
754
|
+
return rows.map(feedbackLoopFromRow);
|
|
755
|
+
}
|
|
756
|
+
async updateFeedbackLoop(runId, loopId, patch, options) {
|
|
757
|
+
await this.ready();
|
|
758
|
+
const sets = ["updated_at = @updated_at"];
|
|
759
|
+
const params = {
|
|
760
|
+
run_id: runId,
|
|
761
|
+
loop_id: loopId,
|
|
762
|
+
updated_at: new Date().toISOString(),
|
|
763
|
+
};
|
|
764
|
+
if (patch.state !== undefined) {
|
|
765
|
+
sets.push("state = @state");
|
|
766
|
+
params.state = patch.state;
|
|
767
|
+
}
|
|
768
|
+
if (patch.current_replay_id !== undefined) {
|
|
769
|
+
sets.push("current_replay_id = @current_replay_id");
|
|
770
|
+
params.current_replay_id = patch.current_replay_id;
|
|
771
|
+
}
|
|
772
|
+
if (patch.current_replay_number !== undefined) {
|
|
773
|
+
sets.push("current_replay_number = @current_replay_number");
|
|
774
|
+
params.current_replay_number = patch.current_replay_number;
|
|
775
|
+
}
|
|
776
|
+
if (patch.policy !== undefined) {
|
|
777
|
+
sets.push("policy_json = @policy_json");
|
|
778
|
+
params.policy_json = JSON.stringify(patch.policy);
|
|
779
|
+
}
|
|
780
|
+
if (patch.deferred_send_back !== undefined) {
|
|
781
|
+
sets.push("deferred_send_back_json = @deferred_send_back_json");
|
|
782
|
+
params.deferred_send_back_json =
|
|
783
|
+
patch.deferred_send_back === null
|
|
784
|
+
? null
|
|
785
|
+
: JSON.stringify(patch.deferred_send_back);
|
|
786
|
+
}
|
|
787
|
+
const whereParts = ["run_id = @run_id", "loop_id = @loop_id"];
|
|
788
|
+
if (options?.expectedState !== undefined) {
|
|
789
|
+
whereParts.push("state = @expected_state");
|
|
790
|
+
params.expected_state = options.expectedState;
|
|
791
|
+
}
|
|
792
|
+
const result = this.db
|
|
793
|
+
.prepare(`UPDATE feedback_loops SET ${sets.join(", ")}
|
|
794
|
+
WHERE ${whereParts.join(" AND ")}`)
|
|
795
|
+
.run(params);
|
|
796
|
+
if (result.changes === 0) {
|
|
797
|
+
if (options?.expectedState !== undefined) {
|
|
798
|
+
const exists = this.db
|
|
799
|
+
.prepare(`SELECT 1 AS ok FROM feedback_loops WHERE run_id = ? AND loop_id = ?`)
|
|
800
|
+
.get(runId, loopId);
|
|
801
|
+
if (exists === undefined) {
|
|
802
|
+
throw new Error(`Feedback loop not found: ${runId}/${loopId}`);
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
throw new Error(`Feedback loop not found: ${runId}/${loopId}`);
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
async createFeedbackReplay(runId, input) {
|
|
811
|
+
await this.ready();
|
|
812
|
+
await this.getFeedbackLoop(runId, input.loop_id);
|
|
813
|
+
const now = new Date().toISOString();
|
|
814
|
+
const status = input.status ?? "scheduled";
|
|
815
|
+
this.db
|
|
816
|
+
.prepare(`INSERT INTO feedback_replays
|
|
817
|
+
(run_id, replay_id, loop_id, source_stage_id, source_attempt, target_stage_id,
|
|
818
|
+
replay_number, max_replays, replay_session, route_stage_ids_json,
|
|
819
|
+
feedback_envelope_json, status, created_at, updated_at)
|
|
820
|
+
VALUES
|
|
821
|
+
(@run_id, @replay_id, @loop_id, @source_stage_id, @source_attempt, @target_stage_id,
|
|
822
|
+
@replay_number, @max_replays, @replay_session, @route_stage_ids_json,
|
|
823
|
+
@feedback_envelope_json, @status, @created_at, @updated_at)`)
|
|
824
|
+
.run({
|
|
825
|
+
run_id: runId,
|
|
826
|
+
replay_id: input.replay_id,
|
|
827
|
+
loop_id: input.loop_id,
|
|
828
|
+
source_stage_id: input.source_stage_id,
|
|
829
|
+
source_attempt: input.source_attempt,
|
|
830
|
+
target_stage_id: input.target_stage_id,
|
|
831
|
+
replay_number: input.replay_number,
|
|
832
|
+
max_replays: input.max_replays,
|
|
833
|
+
replay_session: input.replay_session,
|
|
834
|
+
route_stage_ids_json: JSON.stringify(input.route_stage_ids),
|
|
835
|
+
feedback_envelope_json: JSON.stringify(input.feedback_envelope),
|
|
836
|
+
status,
|
|
837
|
+
created_at: now,
|
|
838
|
+
updated_at: now,
|
|
839
|
+
});
|
|
840
|
+
return this.getFeedbackReplay(runId, input.replay_id);
|
|
841
|
+
}
|
|
842
|
+
async getFeedbackReplay(runId, replayId) {
|
|
843
|
+
await this.ready();
|
|
844
|
+
const row = this.db
|
|
845
|
+
.prepare(`SELECT run_id, replay_id, loop_id, source_stage_id, source_attempt, target_stage_id,
|
|
846
|
+
replay_number, max_replays, replay_session, route_stage_ids_json,
|
|
847
|
+
feedback_envelope_json, status, created_at, updated_at
|
|
848
|
+
FROM feedback_replays WHERE run_id = ? AND replay_id = ?`)
|
|
849
|
+
.get(runId, replayId);
|
|
850
|
+
if (!row) {
|
|
851
|
+
throw new Error(`Feedback replay not found: ${runId}/${replayId}`);
|
|
852
|
+
}
|
|
853
|
+
return feedbackReplayFromRow(row);
|
|
854
|
+
}
|
|
855
|
+
async listFeedbackReplays(runId, loopId) {
|
|
856
|
+
await this.ready();
|
|
857
|
+
await this.getFeedbackLoop(runId, loopId);
|
|
858
|
+
const rows = this.db
|
|
859
|
+
.prepare(`SELECT run_id, replay_id, loop_id, source_stage_id, source_attempt, target_stage_id,
|
|
860
|
+
replay_number, max_replays, replay_session, route_stage_ids_json,
|
|
861
|
+
feedback_envelope_json, status, created_at, updated_at
|
|
862
|
+
FROM feedback_replays
|
|
863
|
+
WHERE run_id = ? AND loop_id = ?
|
|
864
|
+
ORDER BY replay_number ASC`)
|
|
865
|
+
.all(runId, loopId);
|
|
866
|
+
return rows.map(feedbackReplayFromRow);
|
|
867
|
+
}
|
|
868
|
+
async updateFeedbackReplay(runId, replayId, patch) {
|
|
869
|
+
await this.ready();
|
|
870
|
+
const sets = ["updated_at = @updated_at"];
|
|
871
|
+
const params = {
|
|
872
|
+
run_id: runId,
|
|
873
|
+
replay_id: replayId,
|
|
874
|
+
updated_at: new Date().toISOString(),
|
|
875
|
+
};
|
|
876
|
+
if (patch.status !== undefined) {
|
|
877
|
+
sets.push("status = @status");
|
|
878
|
+
params.status = patch.status;
|
|
879
|
+
}
|
|
880
|
+
const result = this.db
|
|
881
|
+
.prepare(`UPDATE feedback_replays SET ${sets.join(", ")}
|
|
882
|
+
WHERE run_id = @run_id AND replay_id = @replay_id`)
|
|
883
|
+
.run(params);
|
|
884
|
+
if (result.changes === 0) {
|
|
885
|
+
throw new Error(`Feedback replay not found: ${runId}/${replayId}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async createFeedbackReplayStagePass(runId, input) {
|
|
889
|
+
await this.ready();
|
|
890
|
+
await this.getFeedbackReplay(runId, input.replay_id);
|
|
891
|
+
const status = input.status ?? "pending";
|
|
892
|
+
this.db
|
|
893
|
+
.prepare(`INSERT INTO feedback_replay_stage_passes
|
|
894
|
+
(run_id, replay_id, stage_id, stage_attempt, session_origin_attempt,
|
|
895
|
+
session_mode, status, started_at, finished_at, emitted_envelope_json)
|
|
896
|
+
VALUES
|
|
897
|
+
(@run_id, @replay_id, @stage_id, @stage_attempt, @session_origin_attempt,
|
|
898
|
+
@session_mode, @status, @started_at, @finished_at, @emitted_envelope_json)`)
|
|
899
|
+
.run({
|
|
900
|
+
run_id: runId,
|
|
901
|
+
replay_id: input.replay_id,
|
|
902
|
+
stage_id: input.stage_id,
|
|
903
|
+
stage_attempt: input.stage_attempt,
|
|
904
|
+
session_origin_attempt: input.session_origin_attempt ?? null,
|
|
905
|
+
session_mode: input.session_mode,
|
|
906
|
+
status,
|
|
907
|
+
started_at: input.started_at ?? null,
|
|
908
|
+
finished_at: input.finished_at ?? null,
|
|
909
|
+
emitted_envelope_json: input.emitted_envelope !== undefined
|
|
910
|
+
? JSON.stringify(input.emitted_envelope)
|
|
911
|
+
: null,
|
|
912
|
+
});
|
|
913
|
+
const row = this.db
|
|
914
|
+
.prepare(`SELECT run_id, replay_id, stage_id, stage_attempt, session_origin_attempt,
|
|
915
|
+
session_mode, status, started_at, finished_at, emitted_envelope_json
|
|
916
|
+
FROM feedback_replay_stage_passes
|
|
917
|
+
WHERE run_id = ? AND replay_id = ? AND stage_id = ?`)
|
|
918
|
+
.get(runId, input.replay_id, input.stage_id);
|
|
919
|
+
return feedbackReplayStagePassFromRow(row);
|
|
920
|
+
}
|
|
921
|
+
async listFeedbackReplayStagePasses(runId, replayId) {
|
|
922
|
+
await this.ready();
|
|
923
|
+
await this.getFeedbackReplay(runId, replayId);
|
|
924
|
+
const rows = this.db
|
|
925
|
+
.prepare(`SELECT run_id, replay_id, stage_id, stage_attempt, session_origin_attempt,
|
|
926
|
+
session_mode, status, started_at, finished_at, emitted_envelope_json
|
|
927
|
+
FROM feedback_replay_stage_passes
|
|
928
|
+
WHERE run_id = ? AND replay_id = ?
|
|
929
|
+
ORDER BY stage_id ASC`)
|
|
930
|
+
.all(runId, replayId);
|
|
931
|
+
return rows.map(feedbackReplayStagePassFromRow);
|
|
932
|
+
}
|
|
933
|
+
async updateFeedbackReplayStagePass(runId, replayId, stageId, patch) {
|
|
934
|
+
await this.ready();
|
|
935
|
+
const sets = [];
|
|
936
|
+
const params = {
|
|
937
|
+
run_id: runId,
|
|
938
|
+
replay_id: replayId,
|
|
939
|
+
stage_id: stageId,
|
|
940
|
+
};
|
|
941
|
+
if (patch.status !== undefined) {
|
|
942
|
+
sets.push("status = @status");
|
|
943
|
+
params.status = patch.status;
|
|
944
|
+
}
|
|
945
|
+
if (patch.stage_attempt !== undefined) {
|
|
946
|
+
sets.push("stage_attempt = @stage_attempt");
|
|
947
|
+
params.stage_attempt = patch.stage_attempt;
|
|
948
|
+
}
|
|
949
|
+
if (patch.started_at !== undefined) {
|
|
950
|
+
sets.push("started_at = @started_at");
|
|
951
|
+
params.started_at = patch.started_at;
|
|
952
|
+
}
|
|
953
|
+
if (patch.finished_at !== undefined) {
|
|
954
|
+
sets.push("finished_at = @finished_at");
|
|
955
|
+
params.finished_at = patch.finished_at;
|
|
956
|
+
}
|
|
957
|
+
if (patch.emitted_envelope !== undefined) {
|
|
958
|
+
sets.push("emitted_envelope_json = @emitted_envelope_json");
|
|
959
|
+
params.emitted_envelope_json =
|
|
960
|
+
patch.emitted_envelope === null
|
|
961
|
+
? null
|
|
962
|
+
: JSON.stringify(patch.emitted_envelope);
|
|
963
|
+
}
|
|
964
|
+
if (sets.length === 0)
|
|
965
|
+
return;
|
|
966
|
+
const result = this.db
|
|
967
|
+
.prepare(`UPDATE feedback_replay_stage_passes SET ${sets.join(", ")}
|
|
968
|
+
WHERE run_id = @run_id AND replay_id = @replay_id AND stage_id = @stage_id`)
|
|
969
|
+
.run(params);
|
|
970
|
+
if (result.changes === 0) {
|
|
971
|
+
throw new Error(`Feedback replay stage pass not found: ${runId}/${replayId}/${stageId}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
async createForkGeneration(runId, input) {
|
|
975
|
+
await this.ready();
|
|
976
|
+
this.getRunRow(runId);
|
|
977
|
+
if (input.replay_id !== undefined) {
|
|
978
|
+
await this.getFeedbackReplay(runId, input.replay_id);
|
|
979
|
+
}
|
|
980
|
+
const now = new Date().toISOString();
|
|
981
|
+
const status = input.status ?? "active";
|
|
982
|
+
this.db
|
|
983
|
+
.prepare(`INSERT INTO fork_generations
|
|
984
|
+
(run_id, generation_id, replay_id, fork_parent_stage_id, generation_number,
|
|
985
|
+
clone_stage_ids_json, status, created_at, updated_at)
|
|
986
|
+
VALUES
|
|
987
|
+
(@run_id, @generation_id, @replay_id, @fork_parent_stage_id, @generation_number,
|
|
988
|
+
@clone_stage_ids_json, @status, @created_at, @updated_at)`)
|
|
989
|
+
.run({
|
|
990
|
+
run_id: runId,
|
|
991
|
+
generation_id: input.generation_id,
|
|
992
|
+
replay_id: input.replay_id ?? null,
|
|
993
|
+
fork_parent_stage_id: input.fork_parent_stage_id,
|
|
994
|
+
generation_number: input.generation_number,
|
|
995
|
+
clone_stage_ids_json: JSON.stringify(input.clone_stage_ids),
|
|
996
|
+
status,
|
|
997
|
+
created_at: now,
|
|
998
|
+
updated_at: now,
|
|
999
|
+
});
|
|
1000
|
+
const row = this.db
|
|
1001
|
+
.prepare(`SELECT run_id, generation_id, replay_id, fork_parent_stage_id, generation_number,
|
|
1002
|
+
clone_stage_ids_json, status, created_at, updated_at
|
|
1003
|
+
FROM fork_generations WHERE run_id = ? AND generation_id = ?`)
|
|
1004
|
+
.get(runId, input.generation_id);
|
|
1005
|
+
return forkGenerationFromRow(row);
|
|
1006
|
+
}
|
|
1007
|
+
async listForkGenerations(runId, options) {
|
|
1008
|
+
await this.ready();
|
|
1009
|
+
this.getRunRow(runId);
|
|
1010
|
+
const clauses = ["run_id = ?"];
|
|
1011
|
+
const params = [runId];
|
|
1012
|
+
if (options?.replayId !== undefined) {
|
|
1013
|
+
clauses.push("replay_id = ?");
|
|
1014
|
+
params.push(options.replayId);
|
|
1015
|
+
}
|
|
1016
|
+
if (options?.forkParentStageId !== undefined) {
|
|
1017
|
+
clauses.push("fork_parent_stage_id = ?");
|
|
1018
|
+
params.push(options.forkParentStageId);
|
|
1019
|
+
}
|
|
1020
|
+
const rows = this.db
|
|
1021
|
+
.prepare(`SELECT run_id, generation_id, replay_id, fork_parent_stage_id, generation_number,
|
|
1022
|
+
clone_stage_ids_json, status, created_at, updated_at
|
|
1023
|
+
FROM fork_generations
|
|
1024
|
+
WHERE ${clauses.join(" AND ")}
|
|
1025
|
+
ORDER BY created_at ASC`)
|
|
1026
|
+
.all(...params);
|
|
1027
|
+
return rows.map(forkGenerationFromRow);
|
|
1028
|
+
}
|
|
1029
|
+
async updateForkGeneration(runId, generationId, patch) {
|
|
1030
|
+
await this.ready();
|
|
1031
|
+
const sets = ["updated_at = @updated_at"];
|
|
1032
|
+
const params = {
|
|
1033
|
+
run_id: runId,
|
|
1034
|
+
generation_id: generationId,
|
|
1035
|
+
updated_at: new Date().toISOString(),
|
|
1036
|
+
};
|
|
1037
|
+
if (patch.status !== undefined) {
|
|
1038
|
+
sets.push("status = @status");
|
|
1039
|
+
params.status = patch.status;
|
|
1040
|
+
}
|
|
1041
|
+
const result = this.db
|
|
1042
|
+
.prepare(`UPDATE fork_generations SET ${sets.join(", ")}
|
|
1043
|
+
WHERE run_id = @run_id AND generation_id = @generation_id`)
|
|
1044
|
+
.run(params);
|
|
1045
|
+
if (result.changes === 0) {
|
|
1046
|
+
throw new Error(`Fork generation not found: ${runId}/${generationId}`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
async loadFeedbackLoopHistory(runId) {
|
|
1050
|
+
const loops = await this.listFeedbackLoops(runId);
|
|
1051
|
+
const allForkGens = await this.listForkGenerations(runId);
|
|
1052
|
+
const nullReplayGens = allForkGens.filter((g) => g.replay_id == null);
|
|
1053
|
+
const history = [];
|
|
1054
|
+
for (const loop of loops) {
|
|
1055
|
+
const replays = await this.listFeedbackReplays(runId, loop.loop_id);
|
|
1056
|
+
const replayEntries = [];
|
|
1057
|
+
for (const replay of replays) {
|
|
1058
|
+
const stage_passes = await this.listFeedbackReplayStagePasses(runId, replay.replay_id);
|
|
1059
|
+
const fork_generations = allForkGens.filter((g) => g.replay_id === replay.replay_id);
|
|
1060
|
+
replayEntries.push({ replay, stage_passes, fork_generations });
|
|
1061
|
+
}
|
|
1062
|
+
history.push({
|
|
1063
|
+
loop,
|
|
1064
|
+
replays: replayEntries,
|
|
1065
|
+
fork_generations: nullReplayGens,
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
const activeCandidates = loops.filter((l) => l.state === "active" || l.state === "waiting_for_human");
|
|
1069
|
+
let active;
|
|
1070
|
+
if (activeCandidates.length > 0) {
|
|
1071
|
+
active = [...activeCandidates].sort((a, b) => {
|
|
1072
|
+
const byUpdated = b.updated_at.localeCompare(a.updated_at);
|
|
1073
|
+
if (byUpdated !== 0)
|
|
1074
|
+
return byUpdated;
|
|
1075
|
+
return b.created_at.localeCompare(a.created_at);
|
|
1076
|
+
})[0];
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
history,
|
|
1080
|
+
...(active !== undefined ? { active } : {}),
|
|
1081
|
+
};
|
|
464
1082
|
}
|
|
465
1083
|
readPipelineDagSnapshotFromRow(row) {
|
|
466
1084
|
if (!row.pipeline_dag_json)
|