@refrainai/cli 0.4.1

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.
Files changed (69) hide show
  1. package/dist/ai-model-FM6GWCID.js +37 -0
  2. package/dist/ai-model-FM6GWCID.js.map +1 -0
  3. package/dist/chunk-2BVDAJZT.js +236 -0
  4. package/dist/chunk-2BVDAJZT.js.map +1 -0
  5. package/dist/chunk-2H7UOFLK.js +11 -0
  6. package/dist/chunk-2H7UOFLK.js.map +1 -0
  7. package/dist/chunk-7UCVPKD4.js +902 -0
  8. package/dist/chunk-7UCVPKD4.js.map +1 -0
  9. package/dist/chunk-AG3CFMYU.js +36 -0
  10. package/dist/chunk-AG3CFMYU.js.map +1 -0
  11. package/dist/chunk-CLYJHKPY.js +1131 -0
  12. package/dist/chunk-CLYJHKPY.js.map +1 -0
  13. package/dist/chunk-D5SI2PHK.js +74 -0
  14. package/dist/chunk-D5SI2PHK.js.map +1 -0
  15. package/dist/chunk-DJVUITRB.js +9084 -0
  16. package/dist/chunk-DJVUITRB.js.map +1 -0
  17. package/dist/chunk-H47NWH7N.js +4427 -0
  18. package/dist/chunk-H47NWH7N.js.map +1 -0
  19. package/dist/chunk-HQDXLWAY.js +109 -0
  20. package/dist/chunk-HQDXLWAY.js.map +1 -0
  21. package/dist/chunk-IGFCYKHC.js +1974 -0
  22. package/dist/chunk-IGFCYKHC.js.map +1 -0
  23. package/dist/chunk-RT664YIO.js +245 -0
  24. package/dist/chunk-RT664YIO.js.map +1 -0
  25. package/dist/chunk-RYIJPYM3.js +164 -0
  26. package/dist/chunk-RYIJPYM3.js.map +1 -0
  27. package/dist/chunk-TDSM3UXI.js +40 -0
  28. package/dist/chunk-TDSM3UXI.js.map +1 -0
  29. package/dist/chunk-UGPXCQY3.js +778 -0
  30. package/dist/chunk-UGPXCQY3.js.map +1 -0
  31. package/dist/chunk-VPK2MQAZ.js +589 -0
  32. package/dist/chunk-VPK2MQAZ.js.map +1 -0
  33. package/dist/chunk-WEYR56ZN.js +953 -0
  34. package/dist/chunk-WEYR56ZN.js.map +1 -0
  35. package/dist/chunk-XMFCXPYU.js +275 -0
  36. package/dist/chunk-XMFCXPYU.js.map +1 -0
  37. package/dist/chunk-Z33FCOTZ.js +251 -0
  38. package/dist/chunk-Z33FCOTZ.js.map +1 -0
  39. package/dist/cli.js +59 -0
  40. package/dist/cli.js.map +1 -0
  41. package/dist/compose-MTSIJY5D.js +547 -0
  42. package/dist/compose-MTSIJY5D.js.map +1 -0
  43. package/dist/config-ZSUNCFXR.js +9 -0
  44. package/dist/config-ZSUNCFXR.js.map +1 -0
  45. package/dist/fix-runbook-ZSBOTLC2.js +294 -0
  46. package/dist/fix-runbook-ZSBOTLC2.js.map +1 -0
  47. package/dist/google-sheets-DRWIVEVC.js +482 -0
  48. package/dist/google-sheets-DRWIVEVC.js.map +1 -0
  49. package/dist/registry-LZLYTNDJ.js +17 -0
  50. package/dist/registry-LZLYTNDJ.js.map +1 -0
  51. package/dist/runbook-data-helpers-KRR2SH76.js +16 -0
  52. package/dist/runbook-data-helpers-KRR2SH76.js.map +1 -0
  53. package/dist/runbook-executor-K7T6RJWJ.js +1480 -0
  54. package/dist/runbook-executor-K7T6RJWJ.js.map +1 -0
  55. package/dist/runbook-generator-MPXJBQ5N.js +800 -0
  56. package/dist/runbook-generator-MPXJBQ5N.js.map +1 -0
  57. package/dist/runbook-schema-3T6TP3JJ.js +35 -0
  58. package/dist/runbook-schema-3T6TP3JJ.js.map +1 -0
  59. package/dist/runbook-store-G5GUOWRR.js +11 -0
  60. package/dist/runbook-store-G5GUOWRR.js.map +1 -0
  61. package/dist/schema-5G6UQSPT.js +91 -0
  62. package/dist/schema-5G6UQSPT.js.map +1 -0
  63. package/dist/server-AG3LXQBI.js +8778 -0
  64. package/dist/server-AG3LXQBI.js.map +1 -0
  65. package/dist/tenant-ai-config-QPFEJUVJ.js +14 -0
  66. package/dist/tenant-ai-config-QPFEJUVJ.js.map +1 -0
  67. package/dist/yaml-patcher-VGUS2JGH.js +15 -0
  68. package/dist/yaml-patcher-VGUS2JGH.js.map +1 -0
  69. package/package.json +37 -0
@@ -0,0 +1,953 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildFlatRunbook,
4
+ copyVersionData,
5
+ insertSteps,
6
+ isDeletable,
7
+ isEditable,
8
+ loadVersionDetail,
9
+ resolveDisplayStatus
10
+ } from "./chunk-Z33FCOTZ.js";
11
+ import {
12
+ jobs,
13
+ runbookSecrets,
14
+ runbookSettings,
15
+ runbookSteps,
16
+ runbookVariables,
17
+ runbookVersionReviews,
18
+ runbookVersions,
19
+ runbooks
20
+ } from "./chunk-CLYJHKPY.js";
21
+
22
+ // src/server/store/runbook-store.ts
23
+ import { eq as eq2, and as and2, desc as desc2, sql as sql2, count as count2 } from "drizzle-orm";
24
+
25
+ // src/server/store/runbook-version-store.ts
26
+ import { eq, and, desc, max, inArray, isNull, count } from "drizzle-orm";
27
+ import { sql } from "drizzle-orm";
28
+ var VersionStore = class {
29
+ constructor(db) {
30
+ this.db = db;
31
+ }
32
+ // ── 作成 ──
33
+ /**
34
+ * 初期バージョン(v1)を作成する。
35
+ * steps ありなら pending_verification、なしなら draft。
36
+ */
37
+ async createInitialVersion(tx, runbookId, data) {
38
+ const hasSteps = data.steps && data.steps.length > 0;
39
+ const status = hasSteps ? "pending_verification" : "draft";
40
+ const [verRow] = await tx.insert(runbookVersions).values({
41
+ runbookId,
42
+ versionNumber: 1,
43
+ status,
44
+ goal: data.goal,
45
+ startUrl: data.startUrl ?? null,
46
+ context: data.context ?? null,
47
+ generatedAt: data.generatedAt ?? null,
48
+ createdBy: data.createdBy ?? null,
49
+ changeDescription: data.changeDescription ?? null
50
+ }).returning();
51
+ if (data.settings) {
52
+ await tx.insert(runbookSettings).values({
53
+ versionId: verRow.id,
54
+ ...data.settings
55
+ });
56
+ }
57
+ if (data.variables) {
58
+ const vars = Object.entries(data.variables).map(([name, def]) => ({
59
+ versionId: verRow.id,
60
+ name,
61
+ source: def.source,
62
+ description: def.description ?? null,
63
+ required: def.required,
64
+ sensitive: def.sensitive,
65
+ value: def.value ?? null
66
+ }));
67
+ if (vars.length > 0) {
68
+ await tx.insert(runbookVariables).values(vars);
69
+ }
70
+ }
71
+ if (data.secrets) {
72
+ const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
73
+ versionId: verRow.id,
74
+ key,
75
+ encryptedValue: val
76
+ }));
77
+ if (secretRows.length > 0) {
78
+ await tx.insert(runbookSecrets).values(secretRows);
79
+ }
80
+ }
81
+ if (data.steps && data.steps.length > 0) {
82
+ await insertSteps(tx, verRow.id, data.steps, null);
83
+ }
84
+ return verRow;
85
+ }
86
+ /**
87
+ * active バージョンからコピーして新 draft 作成。
88
+ * 同時に draft/pending_verification は 1 つまで。
89
+ */
90
+ async createVersionFromActive(tenantId, runbookId, createdBy, changeDescription) {
91
+ const rb = await this.db.query.runbooks.findFirst({
92
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
93
+ });
94
+ if (!rb) return null;
95
+ const existingInProgress = await this.db.query.runbookVersions.findFirst({
96
+ where: and(
97
+ eq(runbookVersions.runbookId, runbookId),
98
+ inArray(runbookVersions.status, ["draft", "pending_verification"])
99
+ )
100
+ });
101
+ if (existingInProgress) {
102
+ return { error: "DRAFT_EXISTS", draftVersionId: existingInProgress.id };
103
+ }
104
+ return await this.db.transaction(async (tx) => {
105
+ const [maxResult] = await tx.select({ maxVer: max(runbookVersions.versionNumber) }).from(runbookVersions).where(eq(runbookVersions.runbookId, runbookId));
106
+ const nextVersion = (maxResult?.maxVer ?? 0) + 1;
107
+ let sourceVersionId = rb.activeVersionId;
108
+ if (!sourceVersionId) {
109
+ const latestVersion = await tx.query.runbookVersions.findFirst({
110
+ where: eq(runbookVersions.runbookId, runbookId),
111
+ orderBy: [desc(runbookVersions.versionNumber)]
112
+ });
113
+ if (latestVersion) {
114
+ sourceVersionId = latestVersion.id;
115
+ }
116
+ }
117
+ let sourceVersion = null;
118
+ if (sourceVersionId) {
119
+ const [sv] = await tx.select().from(runbookVersions).where(eq(runbookVersions.id, sourceVersionId));
120
+ sourceVersion = sv ?? null;
121
+ }
122
+ const [newVer] = await tx.insert(runbookVersions).values({
123
+ runbookId,
124
+ versionNumber: nextVersion,
125
+ status: "draft",
126
+ goal: sourceVersion?.goal ?? "",
127
+ startUrl: sourceVersion?.startUrl ?? null,
128
+ context: sourceVersion?.context ?? null,
129
+ generatedAt: sourceVersion?.generatedAt ?? null,
130
+ createdBy: createdBy ?? null,
131
+ changeDescription: changeDescription ?? null
132
+ }).returning();
133
+ if (sourceVersionId) {
134
+ await copyVersionData(tx, sourceVersionId, newVer.id, { skipSteps: true });
135
+ }
136
+ return { version: newVer };
137
+ });
138
+ }
139
+ // ── 更新 ──
140
+ /**
141
+ * バージョン更新(isEditable ガードつき)
142
+ */
143
+ async updateVersion(tenantId, runbookId, versionId, data) {
144
+ const rb = await this.db.query.runbooks.findFirst({
145
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
146
+ });
147
+ if (!rb) return null;
148
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
149
+ eq(runbookVersions.id, versionId),
150
+ eq(runbookVersions.runbookId, runbookId)
151
+ ));
152
+ if (!ver) return null;
153
+ if (!isEditable(ver.status)) return null;
154
+ return await this.db.transaction(async (tx) => {
155
+ const updates = {};
156
+ if (data.goal !== void 0) updates.goal = data.goal;
157
+ if (data.startUrl !== void 0) updates.startUrl = data.startUrl;
158
+ if (data.context !== void 0) updates.context = data.context;
159
+ if (data.changeDescription !== void 0) updates.changeDescription = data.changeDescription;
160
+ if (ver.status === "pending_verification" && ver.verificationJobId && !data.skipVerificationJobClear) {
161
+ updates.verificationJobId = null;
162
+ }
163
+ if (Object.keys(updates).length > 0) {
164
+ await tx.update(runbookVersions).set(updates).where(eq(runbookVersions.id, versionId));
165
+ }
166
+ if (data.settings !== void 0) {
167
+ await tx.delete(runbookSettings).where(eq(runbookSettings.versionId, versionId));
168
+ if (data.settings) {
169
+ await tx.insert(runbookSettings).values({ versionId, ...data.settings });
170
+ }
171
+ }
172
+ if (data.secrets !== void 0) {
173
+ await tx.delete(runbookSecrets).where(eq(runbookSecrets.versionId, versionId));
174
+ const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
175
+ versionId,
176
+ key,
177
+ encryptedValue: val
178
+ }));
179
+ if (secretRows.length > 0) {
180
+ await tx.insert(runbookSecrets).values(secretRows);
181
+ }
182
+ }
183
+ if (data.variables !== void 0) {
184
+ await tx.delete(runbookVariables).where(eq(runbookVariables.versionId, versionId));
185
+ const vars = Object.entries(data.variables).map(([name, def]) => ({
186
+ versionId,
187
+ name,
188
+ source: def.source,
189
+ description: def.description ?? null,
190
+ required: def.required,
191
+ sensitive: def.sensitive,
192
+ value: def.value ?? null
193
+ }));
194
+ if (vars.length > 0) {
195
+ await tx.insert(runbookVariables).values(vars);
196
+ }
197
+ }
198
+ if (data.steps !== void 0) {
199
+ await tx.delete(runbookSteps).where(eq(runbookSteps.versionId, versionId));
200
+ await insertSteps(tx, versionId, data.steps, null);
201
+ }
202
+ if (data.title !== void 0) {
203
+ await tx.update(runbooks).set({ title: data.title }).where(eq(runbooks.id, runbookId));
204
+ }
205
+ return await this.findById(tenantId, runbookId, versionId);
206
+ });
207
+ }
208
+ // ── 状態遷移 ──
209
+ /**
210
+ * draft → pending_verification
211
+ */
212
+ async completeVersion(tenantId, runbookId, versionId, data) {
213
+ if (data) {
214
+ await this.updateVersion(tenantId, runbookId, versionId, data);
215
+ }
216
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
217
+ eq(runbookVersions.id, versionId),
218
+ eq(runbookVersions.runbookId, runbookId)
219
+ ));
220
+ if (!ver || ver.status !== "draft") return null;
221
+ await this.db.update(runbookVersions).set({ status: "pending_verification" }).where(eq(runbookVersions.id, versionId));
222
+ return await this.findById(tenantId, runbookId, versionId);
223
+ }
224
+ /**
225
+ * pending_verification|pending_approval → active(旧 active を archived)
226
+ */
227
+ async publishVersion(tenantId, runbookId, versionId, publishedBy, maxVersions) {
228
+ const rb = await this.db.query.runbooks.findFirst({
229
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
230
+ });
231
+ if (!rb) return null;
232
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
233
+ eq(runbookVersions.id, versionId),
234
+ eq(runbookVersions.runbookId, runbookId)
235
+ ));
236
+ if (!ver) return null;
237
+ if (ver.status !== "pending_verification" && ver.status !== "pending_approval") {
238
+ return null;
239
+ }
240
+ return await this.db.transaction(async (tx) => {
241
+ if (rb.activeVersionId) {
242
+ if (maxVersions <= 1) {
243
+ await tx.delete(runbookVersions).where(eq(runbookVersions.id, rb.activeVersionId));
244
+ } else {
245
+ await tx.update(runbookVersions).set({ status: "archived" }).where(eq(runbookVersions.id, rb.activeVersionId));
246
+ }
247
+ }
248
+ await tx.update(runbookVersions).set({
249
+ status: "active",
250
+ publishedAt: /* @__PURE__ */ new Date(),
251
+ publishedBy: publishedBy ?? null
252
+ }).where(eq(runbookVersions.id, versionId));
253
+ await tx.update(runbooks).set({ activeVersionId: versionId }).where(eq(runbooks.id, runbookId));
254
+ if (maxVersions > 1) {
255
+ const archivedVersions = await tx.select({ id: runbookVersions.id }).from(runbookVersions).where(and(
256
+ eq(runbookVersions.runbookId, runbookId),
257
+ eq(runbookVersions.status, "archived")
258
+ )).orderBy(desc(runbookVersions.versionNumber));
259
+ const maxArchived = maxVersions - 1;
260
+ if (archivedVersions.length > maxArchived) {
261
+ const toDelete = archivedVersions.slice(maxArchived);
262
+ for (const v of toDelete) {
263
+ await tx.delete(runbookVersions).where(eq(runbookVersions.id, v.id));
264
+ }
265
+ }
266
+ }
267
+ return { status: "active" };
268
+ });
269
+ }
270
+ /**
271
+ * archived → active(現 active を archived)
272
+ */
273
+ async rollbackVersion(tenantId, runbookId, versionId, publishedBy, _maxVersions) {
274
+ const rb = await this.db.query.runbooks.findFirst({
275
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
276
+ });
277
+ if (!rb) return null;
278
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
279
+ eq(runbookVersions.id, versionId),
280
+ eq(runbookVersions.runbookId, runbookId)
281
+ ));
282
+ if (!ver || ver.status !== "archived") return null;
283
+ return await this.db.transaction(async (tx) => {
284
+ if (rb.activeVersionId) {
285
+ await tx.update(runbookVersions).set({ status: "archived" }).where(eq(runbookVersions.id, rb.activeVersionId));
286
+ }
287
+ await tx.update(runbookVersions).set({
288
+ status: "active",
289
+ publishedAt: /* @__PURE__ */ new Date(),
290
+ publishedBy: publishedBy ?? null
291
+ }).where(eq(runbookVersions.id, versionId));
292
+ await tx.update(runbooks).set({ activeVersionId: versionId }).where(eq(runbooks.id, runbookId));
293
+ return { status: "active" };
294
+ });
295
+ }
296
+ // ── 検証ジョブ ──
297
+ async setVerificationJob(versionId, jobId) {
298
+ const result = await this.db.update(runbookVersions).set({ verificationJobId: jobId }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
299
+ return result.length > 0;
300
+ }
301
+ async clearVerificationJob(versionId) {
302
+ const result = await this.db.update(runbookVersions).set({ verificationJobId: null }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
303
+ return result.length > 0;
304
+ }
305
+ /**
306
+ * バージョンのステータスを直接更新(internal use)
307
+ */
308
+ async updateStatus(versionId, status) {
309
+ const result = await this.db.update(runbookVersions).set({ status }).where(eq(runbookVersions.id, versionId)).returning({ id: runbookVersions.id });
310
+ return result.length > 0;
311
+ }
312
+ // ── レビュー ──
313
+ async requestReview(versionId, requestedBy) {
314
+ const [row] = await this.db.insert(runbookVersionReviews).values({
315
+ versionId,
316
+ requestedBy
317
+ }).returning();
318
+ return row;
319
+ }
320
+ async submitReview(reviewId, reviewerId, status, comment) {
321
+ const result = await this.db.update(runbookVersionReviews).set({
322
+ reviewerId,
323
+ reviewedAt: /* @__PURE__ */ new Date(),
324
+ status,
325
+ comment: comment ?? null
326
+ }).where(eq(runbookVersionReviews.id, reviewId)).returning();
327
+ return result.length > 0;
328
+ }
329
+ async getReviews(versionId) {
330
+ return await this.db.query.runbookVersionReviews.findMany({
331
+ where: eq(runbookVersionReviews.versionId, versionId)
332
+ });
333
+ }
334
+ // ── クエリ ──
335
+ /**
336
+ * バージョン詳細取得
337
+ */
338
+ async findById(tenantId, runbookId, versionId) {
339
+ const rb = await this.db.query.runbooks.findFirst({
340
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId)),
341
+ columns: { id: true, activeVersionId: true }
342
+ });
343
+ if (!rb) return null;
344
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
345
+ eq(runbookVersions.id, versionId),
346
+ eq(runbookVersions.runbookId, runbookId)
347
+ ));
348
+ if (!ver) return null;
349
+ const detail = await loadVersionDetail(this.db, versionId);
350
+ let verificationJobStatus = null;
351
+ if (ver.verificationJobId) {
352
+ const [job] = await this.db.select({ status: jobs.status }).from(jobs).where(eq(jobs.id, ver.verificationJobId));
353
+ verificationJobStatus = job?.status ?? null;
354
+ }
355
+ return {
356
+ ...ver,
357
+ isActive: rb.activeVersionId === versionId,
358
+ stepsCount: detail.steps.filter((s) => !s.parentStepId).length,
359
+ verificationJobStatus,
360
+ displayStatus: resolveDisplayStatus(ver.status, ver.verificationJobId, verificationJobStatus),
361
+ ...detail
362
+ };
363
+ }
364
+ /**
365
+ * バージョン一覧
366
+ */
367
+ async list(tenantId, runbookId) {
368
+ const rb = await this.db.query.runbooks.findFirst({
369
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId)),
370
+ columns: { id: true, activeVersionId: true }
371
+ });
372
+ if (!rb) return null;
373
+ const versions = await this.db.select({
374
+ id: runbookVersions.id,
375
+ versionNumber: runbookVersions.versionNumber,
376
+ status: runbookVersions.status,
377
+ goal: runbookVersions.goal,
378
+ startUrl: runbookVersions.startUrl,
379
+ generatedAt: runbookVersions.generatedAt,
380
+ verificationJobId: runbookVersions.verificationJobId,
381
+ verificationJobStatus: sql`(
382
+ SELECT j.status FROM jobs j
383
+ WHERE j.id = ${runbookVersions.verificationJobId}
384
+ )`.as("verificationJobStatus"),
385
+ createdBy: runbookVersions.createdBy,
386
+ createdAt: runbookVersions.createdAt,
387
+ publishedAt: runbookVersions.publishedAt,
388
+ changeDescription: runbookVersions.changeDescription
389
+ }).from(runbookVersions).where(eq(runbookVersions.runbookId, runbookId)).orderBy(desc(runbookVersions.versionNumber));
390
+ if (versions.length === 0) return [];
391
+ const stepCounts = await this.db.select({
392
+ versionId: runbookSteps.versionId,
393
+ count: count()
394
+ }).from(runbookSteps).where(
395
+ and(
396
+ inArray(runbookSteps.versionId, versions.map((v) => v.id)),
397
+ isNull(runbookSteps.parentStepId)
398
+ )
399
+ ).groupBy(runbookSteps.versionId);
400
+ const countMap = new Map(stepCounts.map((sc) => [sc.versionId, sc.count]));
401
+ return versions.map((v) => ({
402
+ ...v,
403
+ stepsCount: countMap.get(v.id) ?? 0,
404
+ isActive: rb.activeVersionId === v.id,
405
+ displayStatus: resolveDisplayStatus(v.status, v.verificationJobId, v.verificationJobStatus)
406
+ }));
407
+ }
408
+ /**
409
+ * バージョン削除(isDeletable ガードつき)
410
+ */
411
+ async deleteVersion(tenantId, runbookId, versionId) {
412
+ const rb = await this.db.query.runbooks.findFirst({
413
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
414
+ });
415
+ if (!rb) return false;
416
+ const [ver] = await this.db.select().from(runbookVersions).where(and(
417
+ eq(runbookVersions.id, versionId),
418
+ eq(runbookVersions.runbookId, runbookId)
419
+ ));
420
+ if (!ver || !isDeletable(ver.status)) return false;
421
+ await this.db.delete(runbookVersions).where(eq(runbookVersions.id, versionId));
422
+ return true;
423
+ }
424
+ /**
425
+ * ステップの部分更新
426
+ */
427
+ async patchSteps(tenantId, runbookId, versionId, patches) {
428
+ const rb = await this.db.query.runbooks.findFirst({
429
+ where: and(eq(runbooks.id, runbookId), eq(runbooks.tenantId, tenantId))
430
+ });
431
+ if (!rb) return false;
432
+ await this.db.transaction(async (tx) => {
433
+ for (const patch of patches) {
434
+ const updates = {};
435
+ if (patch.description !== void 0) updates.description = patch.description;
436
+ if (patch.riskLevel !== void 0) updates.riskLevel = patch.riskLevel;
437
+ if (patch.requiresConfirmation !== void 0) updates.requiresConfirmation = patch.requiresConfirmation;
438
+ if (Object.keys(updates).length > 0) {
439
+ await tx.update(runbookSteps).set(updates).where(and(eq(runbookSteps.id, patch.id), eq(runbookSteps.versionId, versionId)));
440
+ }
441
+ }
442
+ });
443
+ return true;
444
+ }
445
+ };
446
+
447
+ // src/server/store/runbook-store.ts
448
+ var RunbookStore = class {
449
+ constructor(db) {
450
+ this.db = db;
451
+ this.versions = new VersionStore(db);
452
+ }
453
+ // ── 作成 ──
454
+ /**
455
+ * Runbook + 初期バージョン(v1)を作成する。
456
+ * ParsedRunbook を含むフル作成。ステータスは pending_verification。
457
+ */
458
+ async insert(data) {
459
+ const { tenantId, createdBy, runbook, secrets, context, settings } = data;
460
+ return await this.db.transaction(async (tx) => {
461
+ const [rbRow] = await tx.insert(runbooks).values({
462
+ tenantId,
463
+ title: runbook.title,
464
+ createdBy: createdBy ?? null
465
+ }).returning();
466
+ const verRow = await this.versions.createInitialVersion(tx, rbRow.id, {
467
+ goal: runbook.metadata.goal,
468
+ startUrl: runbook.metadata.startUrl,
469
+ context: context ?? null,
470
+ generatedAt: runbook.metadata.generatedAt,
471
+ createdBy: createdBy ?? null,
472
+ settings,
473
+ secrets,
474
+ variables: runbook.variables,
475
+ steps: runbook.steps
476
+ });
477
+ await tx.update(runbooks).set({ activeVersionId: verRow.id }).where(eq2(runbooks.id, rbRow.id));
478
+ return { ...rbRow, activeVersionId: verRow.id };
479
+ });
480
+ }
481
+ /**
482
+ * Draft の Runbook + 初期 draft バージョンを作成する。
483
+ */
484
+ async insertDraft(data) {
485
+ return await this.db.transaction(async (tx) => {
486
+ const [rbRow] = await tx.insert(runbooks).values({
487
+ tenantId: data.tenantId,
488
+ title: data.title,
489
+ createdBy: data.createdBy ?? null
490
+ }).returning();
491
+ const verRow = await this.versions.createInitialVersion(tx, rbRow.id, {
492
+ goal: data.goal ?? "",
493
+ startUrl: data.startUrl ?? null,
494
+ context: data.context ?? null,
495
+ createdBy: data.createdBy ?? null,
496
+ secrets: data.secrets,
497
+ variables: data.variables
498
+ });
499
+ return { ...rbRow, versionId: verRow.id };
500
+ });
501
+ }
502
+ // ── クエリ ──
503
+ /**
504
+ * Runbook 詳細取得(active バージョンの情報を含む)
505
+ * 旧 API との互換性を維持するためにフラット構造で返す。
506
+ */
507
+ async findById(tenantId, id) {
508
+ const row = await this.db.query.runbooks.findFirst({
509
+ where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
510
+ });
511
+ if (!row) return null;
512
+ const versionId = row.activeVersionId;
513
+ if (!versionId) {
514
+ const latestDraft = await this.db.query.runbookVersions.findFirst({
515
+ where: eq2(runbookVersions.runbookId, id),
516
+ orderBy: [desc2(runbookVersions.versionNumber)]
517
+ });
518
+ if (!latestDraft) {
519
+ const status = row.disabled ? "disabled" : "draft";
520
+ return {
521
+ ...row,
522
+ goal: "",
523
+ startUrl: null,
524
+ context: null,
525
+ generatedAt: null,
526
+ status,
527
+ verificationJobId: null,
528
+ verificationJobStatus: null,
529
+ displayStatus: resolveDisplayStatus(status, null, null),
530
+ settings: null,
531
+ variables: [],
532
+ secrets: [],
533
+ steps: [],
534
+ activeVersionId: null,
535
+ currentVersionId: null
536
+ };
537
+ }
538
+ return await buildFlatRunbook(this.db, row, latestDraft);
539
+ }
540
+ const [ver] = await this.db.select().from(runbookVersions).where(eq2(runbookVersions.id, versionId));
541
+ if (!ver) return null;
542
+ return await buildFlatRunbook(this.db, row, ver);
543
+ }
544
+ /**
545
+ * Runbook 一覧
546
+ */
547
+ async list(tenantId, opts = {}) {
548
+ const { limit = 50, offset = 0, status, includeDisabled = false } = opts;
549
+ const conditions = [eq2(runbooks.tenantId, tenantId)];
550
+ if (status === "disabled") {
551
+ conditions.push(eq2(runbooks.disabled, true));
552
+ } else if (status) {
553
+ conditions.push(eq2(runbooks.disabled, false));
554
+ } else if (!includeDisabled) {
555
+ conditions.push(eq2(runbooks.disabled, false));
556
+ }
557
+ const where = and2(...conditions);
558
+ const rows = await this.db.select({
559
+ id: runbooks.id,
560
+ title: runbooks.title,
561
+ disabled: runbooks.disabled,
562
+ activeVersionId: runbooks.activeVersionId,
563
+ createdAt: runbooks.createdAt,
564
+ updatedAt: runbooks.updatedAt,
565
+ goal: sql2`COALESCE((
566
+ SELECT rv.goal FROM runbook_versions rv
567
+ WHERE rv.id = ${runbooks.activeVersionId}
568
+ ), (
569
+ SELECT rv.goal FROM runbook_versions rv
570
+ WHERE rv.runbook_id = ${runbooks.id}
571
+ ORDER BY rv.version_number DESC LIMIT 1
572
+ ), '')`.as("goal"),
573
+ startUrl: sql2`COALESCE((
574
+ SELECT rv.start_url FROM runbook_versions rv
575
+ WHERE rv.id = ${runbooks.activeVersionId}
576
+ ), (
577
+ SELECT rv.start_url FROM runbook_versions rv
578
+ WHERE rv.runbook_id = ${runbooks.id}
579
+ ORDER BY rv.version_number DESC LIMIT 1
580
+ ))`.as("startUrl"),
581
+ generatedAt: sql2`COALESCE((
582
+ SELECT rv.generated_at FROM runbook_versions rv
583
+ WHERE rv.id = ${runbooks.activeVersionId}
584
+ ), (
585
+ SELECT rv.generated_at FROM runbook_versions rv
586
+ WHERE rv.runbook_id = ${runbooks.id}
587
+ ORDER BY rv.version_number DESC LIMIT 1
588
+ ))`.as("generatedAt"),
589
+ versionStatus: sql2`COALESCE((
590
+ SELECT rv.status FROM runbook_versions rv
591
+ WHERE rv.id = ${runbooks.activeVersionId}
592
+ ), (
593
+ SELECT rv.status FROM runbook_versions rv
594
+ WHERE rv.runbook_id = ${runbooks.id}
595
+ ORDER BY rv.version_number DESC LIMIT 1
596
+ ))`.as("versionStatus"),
597
+ verificationJobId: sql2`COALESCE((
598
+ SELECT rv.verification_job_id FROM runbook_versions rv
599
+ WHERE rv.id = ${runbooks.activeVersionId}
600
+ ), (
601
+ SELECT rv.verification_job_id FROM runbook_versions rv
602
+ WHERE rv.runbook_id = ${runbooks.id}
603
+ ORDER BY rv.version_number DESC LIMIT 1
604
+ ))`.as("verificationJobId"),
605
+ stepsCount: sql2`COALESCE((
606
+ SELECT count(*)::int FROM runbook_steps rs
607
+ JOIN runbook_versions rv ON rv.id = rs.version_id
608
+ WHERE rv.id = COALESCE(${runbooks.activeVersionId}, (
609
+ SELECT rv2.id FROM runbook_versions rv2
610
+ WHERE rv2.runbook_id = ${runbooks.id}
611
+ ORDER BY rv2.version_number DESC LIMIT 1
612
+ ))
613
+ AND rs.parent_step_id IS NULL
614
+ ), 0)`.as("stepsCount"),
615
+ lastJobStatus: sql2`(
616
+ SELECT ${jobs.status} FROM ${jobs}
617
+ WHERE ${jobs.runbookId} = ${runbooks.id}
618
+ AND ${jobs.mode} IN ('execute', 'self_heal')
619
+ ORDER BY ${jobs.createdAt} DESC
620
+ LIMIT 1
621
+ )`.as("lastJobStatus"),
622
+ lastJobCompletedAt: sql2`(
623
+ SELECT ${jobs.completedAt} FROM ${jobs}
624
+ WHERE ${jobs.runbookId} = ${runbooks.id}
625
+ AND ${jobs.mode} IN ('execute', 'self_heal')
626
+ ORDER BY ${jobs.createdAt} DESC
627
+ LIMIT 1
628
+ )`.as("lastJobCompletedAt"),
629
+ verificationJobStatus: sql2`(
630
+ SELECT j.status FROM jobs j
631
+ WHERE j.id = COALESCE((
632
+ SELECT rv.verification_job_id FROM runbook_versions rv
633
+ WHERE rv.id = ${runbooks.activeVersionId}
634
+ ), (
635
+ SELECT rv.verification_job_id FROM runbook_versions rv
636
+ WHERE rv.runbook_id = ${runbooks.id}
637
+ ORDER BY rv.version_number DESC LIMIT 1
638
+ ))
639
+ )`.as("verificationJobStatus"),
640
+ versionCount: sql2`(
641
+ SELECT count(*)::int FROM runbook_versions rv
642
+ WHERE rv.runbook_id = ${runbooks.id}
643
+ )`.as("versionCount"),
644
+ hasDraft: sql2`EXISTS(
645
+ SELECT 1 FROM runbook_versions rv
646
+ WHERE rv.runbook_id = ${runbooks.id} AND rv.status = 'draft'
647
+ )`.as("hasDraft")
648
+ }).from(runbooks).where(where).orderBy(desc2(runbooks.createdAt)).limit(limit).offset(offset);
649
+ let filteredRows = rows;
650
+ if (status && status !== "disabled") {
651
+ const mappedStatus = status === "active" ? "active" : status === "pending_verification" ? "pending_verification" : status;
652
+ filteredRows = rows.filter((r) => r.versionStatus === mappedStatus);
653
+ }
654
+ const items = filteredRows.map((r) => {
655
+ const itemStatus = r.disabled ? "disabled" : r.versionStatus ?? "draft";
656
+ return {
657
+ id: r.id,
658
+ title: r.title,
659
+ goal: r.goal ?? "",
660
+ startUrl: r.startUrl,
661
+ generatedAt: r.generatedAt ?? "",
662
+ status: itemStatus,
663
+ verificationJobId: r.verificationJobId,
664
+ verificationJobStatus: r.verificationJobStatus,
665
+ displayStatus: resolveDisplayStatus(itemStatus, r.verificationJobId, r.verificationJobStatus),
666
+ createdAt: r.createdAt,
667
+ updatedAt: r.updatedAt,
668
+ stepsCount: r.stepsCount,
669
+ lastJobStatus: r.lastJobStatus,
670
+ lastJobCompletedAt: r.lastJobCompletedAt,
671
+ versionCount: r.versionCount,
672
+ hasDraft: r.hasDraft,
673
+ activeVersionId: r.activeVersionId
674
+ };
675
+ });
676
+ const [totalResult] = await this.db.select({ count: count2() }).from(runbooks).where(where);
677
+ return { items, total: totalResult.count };
678
+ }
679
+ // ── 更新 ──
680
+ /**
681
+ * Runbook の active バージョンを更新する(旧 API 互換)。
682
+ */
683
+ async update(tenantId, id, data) {
684
+ const existing = await this.findById(tenantId, id);
685
+ if (!existing) return null;
686
+ const versionId = existing.currentVersionId;
687
+ if (!versionId) return null;
688
+ return await this.db.transaction(async (tx) => {
689
+ if (data.runbook) {
690
+ const rb = data.runbook;
691
+ const versionStatus = existing.status;
692
+ const statusReset = versionStatus === "active" && !data.skipStatusReset ? { status: "pending_verification", verificationJobId: null } : versionStatus === "draft" ? { status: "pending_verification" } : {};
693
+ await tx.update(runbookVersions).set({
694
+ goal: rb.metadata.goal,
695
+ startUrl: rb.metadata.startUrl,
696
+ context: data.context ?? existing.context,
697
+ generatedAt: rb.metadata.generatedAt,
698
+ ...statusReset
699
+ }).where(eq2(runbookVersions.id, versionId));
700
+ await tx.update(runbooks).set({ title: rb.title }).where(eq2(runbooks.id, id));
701
+ const nextVariables = data.variables ?? rb.variables;
702
+ if (nextVariables !== void 0) {
703
+ await tx.delete(runbookVariables).where(eq2(runbookVariables.versionId, versionId));
704
+ const vars = Object.entries(nextVariables).map(([name, def]) => ({
705
+ versionId,
706
+ name,
707
+ source: def.source,
708
+ description: def.description ?? null,
709
+ required: def.required,
710
+ sensitive: def.sensitive,
711
+ value: def.value ?? null
712
+ }));
713
+ if (vars.length > 0) {
714
+ await tx.insert(runbookVariables).values(vars);
715
+ }
716
+ }
717
+ await tx.delete(runbookSteps).where(eq2(runbookSteps.versionId, versionId));
718
+ await insertSteps(tx, versionId, rb.steps, null);
719
+ } else if (data.context !== void 0) {
720
+ await tx.update(runbookVersions).set({ context: data.context }).where(eq2(runbookVersions.id, versionId));
721
+ }
722
+ if (!data.runbook && data.variables !== void 0) {
723
+ await tx.delete(runbookVariables).where(eq2(runbookVariables.versionId, versionId));
724
+ const vars = Object.entries(data.variables).map(([name, def]) => ({
725
+ versionId,
726
+ name,
727
+ source: def.source,
728
+ description: def.description ?? null,
729
+ required: def.required,
730
+ sensitive: def.sensitive,
731
+ value: def.value ?? null
732
+ }));
733
+ if (vars.length > 0) {
734
+ await tx.insert(runbookVariables).values(vars);
735
+ }
736
+ }
737
+ if (data.settings !== void 0) {
738
+ await tx.delete(runbookSettings).where(eq2(runbookSettings.versionId, versionId));
739
+ if (data.settings) {
740
+ await tx.insert(runbookSettings).values({ versionId, ...data.settings });
741
+ }
742
+ }
743
+ if (data.secrets !== void 0) {
744
+ await tx.delete(runbookSecrets).where(eq2(runbookSecrets.versionId, versionId));
745
+ const secretRows = Object.entries(data.secrets).map(([key, val]) => ({
746
+ versionId,
747
+ key,
748
+ encryptedValue: val
749
+ }));
750
+ if (secretRows.length > 0) {
751
+ await tx.insert(runbookSecrets).values(secretRows);
752
+ }
753
+ }
754
+ return await this.findById(tenantId, id);
755
+ });
756
+ }
757
+ /**
758
+ * updateDraft: draft/pending_verification ステータスのバージョンを更新
759
+ */
760
+ async updateDraft(tenantId, id, data) {
761
+ const existing = await this.findById(tenantId, id);
762
+ if (!existing) return null;
763
+ const versionId = existing.currentVersionId;
764
+ if (!versionId) return null;
765
+ if (!isEditable(existing.versionStatus)) return null;
766
+ const result = await this.versions.updateVersion(tenantId, id, versionId, {
767
+ ...data
768
+ });
769
+ if (!result) return null;
770
+ return await this.findById(tenantId, id);
771
+ }
772
+ /**
773
+ * completeDraft: draft → pending_verification
774
+ */
775
+ async completeDraft(tenantId, id, data) {
776
+ const existing = await this.findById(tenantId, id);
777
+ if (!existing) return null;
778
+ const versionId = existing.currentVersionId;
779
+ if (!versionId) return null;
780
+ const result = await this.versions.completeVersion(tenantId, id, versionId, data);
781
+ if (!result) return null;
782
+ return await this.findById(tenantId, id);
783
+ }
784
+ /**
785
+ * updateStatus: 旧 API 互換。disabled フラグとバージョンステータスを操作。
786
+ */
787
+ async updateStatus(tenantId, id, status) {
788
+ if (status === "disabled") {
789
+ const result = await this.db.update(runbooks).set({ disabled: true }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
790
+ return result.length > 0;
791
+ }
792
+ if (status === "active") {
793
+ const rb2 = await this.db.query.runbooks.findFirst({
794
+ where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
795
+ });
796
+ if (!rb2) return false;
797
+ if (rb2.disabled) {
798
+ await this.db.update(runbooks).set({ disabled: false }).where(eq2(runbooks.id, id));
799
+ return true;
800
+ }
801
+ if (rb2.activeVersionId) {
802
+ const [ver] = await this.db.select().from(runbookVersions).where(eq2(runbookVersions.id, rb2.activeVersionId));
803
+ if (ver && ver.status === "pending_verification") {
804
+ await this.db.update(runbookVersions).set({ status: "active", publishedAt: /* @__PURE__ */ new Date() }).where(eq2(runbookVersions.id, rb2.activeVersionId));
805
+ return true;
806
+ }
807
+ }
808
+ const latestVer = await this.db.query.runbookVersions.findFirst({
809
+ where: and2(
810
+ eq2(runbookVersions.runbookId, id),
811
+ eq2(runbookVersions.status, "pending_verification")
812
+ ),
813
+ orderBy: [desc2(runbookVersions.versionNumber)]
814
+ });
815
+ if (latestVer) {
816
+ await this.db.update(runbookVersions).set({ status: "active", publishedAt: /* @__PURE__ */ new Date() }).where(eq2(runbookVersions.id, latestVer.id));
817
+ await this.db.update(runbooks).set({ activeVersionId: latestVer.id }).where(eq2(runbooks.id, id));
818
+ return true;
819
+ }
820
+ return false;
821
+ }
822
+ const rb = await this.db.query.runbooks.findFirst({
823
+ where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))
824
+ });
825
+ if (!rb) return false;
826
+ const targetVersionId = rb.activeVersionId;
827
+ if (!targetVersionId) {
828
+ const latestVer = await this.db.query.runbookVersions.findFirst({
829
+ where: eq2(runbookVersions.runbookId, id),
830
+ orderBy: [desc2(runbookVersions.versionNumber)]
831
+ });
832
+ if (latestVer) {
833
+ await this.db.update(runbookVersions).set({ status }).where(eq2(runbookVersions.id, latestVer.id));
834
+ return true;
835
+ }
836
+ return false;
837
+ }
838
+ await this.db.update(runbookVersions).set({ status }).where(eq2(runbookVersions.id, targetVersionId));
839
+ return true;
840
+ }
841
+ /**
842
+ * getStatus: 旧 API 互換
843
+ */
844
+ async getStatus(tenantId, id) {
845
+ const rb = await this.db.query.runbooks.findFirst({
846
+ where: and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId)),
847
+ columns: { id: true, disabled: true, activeVersionId: true }
848
+ });
849
+ if (!rb) return null;
850
+ if (rb.disabled) return "disabled";
851
+ const verId = rb.activeVersionId;
852
+ if (verId) {
853
+ const [ver] = await this.db.select({ status: runbookVersions.status }).from(runbookVersions).where(eq2(runbookVersions.id, verId));
854
+ if (ver) return ver.status;
855
+ }
856
+ const latestVer = await this.db.query.runbookVersions.findFirst({
857
+ where: eq2(runbookVersions.runbookId, id),
858
+ orderBy: [desc2(runbookVersions.versionNumber)],
859
+ columns: { status: true }
860
+ });
861
+ return latestVer?.status ?? "draft";
862
+ }
863
+ /**
864
+ * setVerificationJob: 旧 API 互換
865
+ */
866
+ async setVerificationJob(tenantId, runbookId, jobId) {
867
+ const rb = await this.db.query.runbooks.findFirst({
868
+ where: and2(eq2(runbooks.id, runbookId), eq2(runbooks.tenantId, tenantId))
869
+ });
870
+ if (!rb) return false;
871
+ const targetVer = await this.db.query.runbookVersions.findFirst({
872
+ where: eq2(runbookVersions.runbookId, runbookId),
873
+ orderBy: [desc2(runbookVersions.versionNumber)]
874
+ });
875
+ if (!targetVer) return false;
876
+ return await this.versions.setVerificationJob(targetVer.id, jobId);
877
+ }
878
+ /**
879
+ * clearVerificationJob: 旧 API 互換
880
+ */
881
+ async clearVerificationJob(tenantId, runbookId) {
882
+ const targetVer = await this.db.query.runbookVersions.findFirst({
883
+ where: eq2(runbookVersions.runbookId, runbookId),
884
+ orderBy: [desc2(runbookVersions.versionNumber)]
885
+ });
886
+ if (!targetVer) return false;
887
+ return await this.versions.clearVerificationJob(targetVer.id);
888
+ }
889
+ // ── ライフサイクル ──
890
+ /**
891
+ * duplicate: Runbook をコピーする(active バージョンから draft 作成)
892
+ */
893
+ async duplicate(tenantId, sourceId, createdBy) {
894
+ const source = await this.findById(tenantId, sourceId);
895
+ if (!source) return null;
896
+ return await this.db.transaction(async (tx) => {
897
+ const [newRb] = await tx.insert(runbooks).values({
898
+ tenantId,
899
+ title: `${source.title} (Copy)`,
900
+ createdBy: createdBy ?? null
901
+ }).returning();
902
+ const verRow = await this.versions.createInitialVersion(tx, newRb.id, {
903
+ goal: source.goal,
904
+ startUrl: source.startUrl,
905
+ context: source.context,
906
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
907
+ createdBy: createdBy ?? null
908
+ });
909
+ if (source.currentVersionId) {
910
+ const { copyVersionData: copyVersionData2 } = await import("./runbook-data-helpers-KRR2SH76.js");
911
+ await copyVersionData2(tx, source.currentVersionId, verRow.id);
912
+ }
913
+ return { id: newRb.id };
914
+ });
915
+ }
916
+ /**
917
+ * patchSteps: ステップの部分更新(最新バージョン対象)
918
+ */
919
+ async patchSteps(tenantId, runbookId, patches) {
920
+ const targetVer = await this.db.query.runbookVersions.findFirst({
921
+ where: eq2(runbookVersions.runbookId, runbookId),
922
+ orderBy: [desc2(runbookVersions.versionNumber)]
923
+ });
924
+ if (!targetVer) return false;
925
+ return await this.versions.patchSteps(tenantId, runbookId, targetVer.id, patches);
926
+ }
927
+ /**
928
+ * delete: Runbook を削除(CASCADE で全バージョン・子テーブルも削除)
929
+ */
930
+ async delete(tenantId, id) {
931
+ const result = await this.db.delete(runbooks).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
932
+ return result.length > 0;
933
+ }
934
+ /**
935
+ * disable: Runbook を無効化
936
+ */
937
+ async disable(tenantId, id) {
938
+ const result = await this.db.update(runbooks).set({ disabled: true }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
939
+ return result.length > 0;
940
+ }
941
+ /**
942
+ * enable: Runbook を有効化
943
+ */
944
+ async enable(tenantId, id) {
945
+ const result = await this.db.update(runbooks).set({ disabled: false }).where(and2(eq2(runbooks.id, id), eq2(runbooks.tenantId, tenantId))).returning({ id: runbooks.id });
946
+ return result.length > 0;
947
+ }
948
+ };
949
+
950
+ export {
951
+ RunbookStore
952
+ };
953
+ //# sourceMappingURL=chunk-WEYR56ZN.js.map