@contractspec/lib.runtime-sandbox 2.7.6 → 2.7.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/README.md +42 -22
- package/dist/browser/index.js +364 -120
- package/dist/index.js +364 -120
- package/dist/node/index.js +364 -120
- package/dist/web/index.d.ts +3 -3
- package/package.json +7 -7
- package/src/adapters/pglite/adapter.ts +132 -132
- package/src/index.ts +9 -9
- package/src/ports/database.port.ts +53 -53
- package/src/ports/index.ts +2 -2
- package/src/types/database.types.ts +22 -22
- package/src/web/database/migrations.ts +195 -195
- package/src/web/database/schema.ts +419 -419
- package/src/web/events/local-pubsub.ts +22 -22
- package/src/web/graphql/local-client.ts +495 -496
- package/src/web/index.ts +6 -11
- package/src/web/runtime/seeders/index.ts +611 -352
- package/src/web/runtime/services.ts +104 -105
- package/src/web/storage/indexeddb.ts +98 -98
- package/src/web/utils/id.ts +5 -5
|
@@ -7,9 +7,9 @@ import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
|
|
|
7
7
|
import type { TemplateId } from '../services';
|
|
8
8
|
|
|
9
9
|
export interface SeedTemplateParams {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
templateId: TemplateId;
|
|
11
|
+
projectId: string;
|
|
12
|
+
db: DatabasePort;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -18,35 +18,35 @@ export interface SeedTemplateParams {
|
|
|
18
18
|
* Unknown templates are a no-op (safe default).
|
|
19
19
|
*/
|
|
20
20
|
export async function seedTemplate(params: SeedTemplateParams): Promise<void> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
21
|
+
const { templateId, projectId, db } = params;
|
|
22
|
+
|
|
23
|
+
switch (templateId) {
|
|
24
|
+
case 'todos-app':
|
|
25
|
+
return seedTodos({ projectId, db });
|
|
26
|
+
case 'messaging-app':
|
|
27
|
+
return seedMessaging({ projectId, db });
|
|
28
|
+
case 'recipe-app-i18n':
|
|
29
|
+
return seedRecipes({ projectId, db });
|
|
30
|
+
case 'crm-pipeline':
|
|
31
|
+
return seedCrmPipeline({ projectId, db });
|
|
32
|
+
case 'saas-boilerplate':
|
|
33
|
+
return seedSaasBoilerplate({ projectId, db });
|
|
34
|
+
case 'agent-console':
|
|
35
|
+
return seedAgentConsole({ projectId, db });
|
|
36
|
+
case 'workflow-system':
|
|
37
|
+
return seedWorkflowSystem({ projectId, db });
|
|
38
|
+
case 'marketplace':
|
|
39
|
+
return seedMarketplace({ projectId, db });
|
|
40
|
+
case 'integration-hub':
|
|
41
|
+
return seedIntegrationHub({ projectId, db });
|
|
42
|
+
case 'analytics-dashboard':
|
|
43
|
+
return seedAnalyticsDashboard({ projectId, db });
|
|
44
|
+
case 'policy-safe-knowledge-assistant':
|
|
45
|
+
return seedPolicyKnowledgeAssistant({ projectId, db });
|
|
46
|
+
default:
|
|
47
|
+
// Unknown template - no-op
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// --------------------------------------------------------------------------
|
|
@@ -54,396 +54,655 @@ export async function seedTemplate(params: SeedTemplateParams): Promise<void> {
|
|
|
54
54
|
// --------------------------------------------------------------------------
|
|
55
55
|
|
|
56
56
|
async function seedTodos(params: { projectId: string; db: DatabasePort }) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
57
|
+
const { projectId, db } = params;
|
|
58
|
+
|
|
59
|
+
// Check if already seeded
|
|
60
|
+
const existing = await db.query(
|
|
61
|
+
`SELECT COUNT(*) as count FROM template_task WHERE "projectId" = $1`,
|
|
62
|
+
[projectId]
|
|
63
|
+
);
|
|
64
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
65
|
+
|
|
66
|
+
const workCategoryId = 'todo_category_ops';
|
|
67
|
+
const homeCategoryId = 'todo_category_home';
|
|
68
|
+
|
|
69
|
+
await db.execute(
|
|
70
|
+
`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`,
|
|
71
|
+
[workCategoryId, projectId, 'Operations', '#8b5cf6']
|
|
72
|
+
);
|
|
73
|
+
await db.execute(
|
|
74
|
+
`INSERT INTO template_task_category (id, "projectId", name, color) VALUES ($1, $2, $3, $4)`,
|
|
75
|
+
[homeCategoryId, projectId, 'Home', '#f472b6']
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const tasks = [
|
|
79
|
+
{
|
|
80
|
+
id: 'todo_task_1',
|
|
81
|
+
title: 'Review intent signals',
|
|
82
|
+
description: "Scan yesterday's signals and flag the ones to promote.",
|
|
83
|
+
categoryId: workCategoryId,
|
|
84
|
+
priority: 'HIGH',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'todo_task_2',
|
|
88
|
+
title: 'Schedule studio walkthrough',
|
|
89
|
+
description: "Prep the sandbox before tomorrow's ceremony.",
|
|
90
|
+
categoryId: workCategoryId,
|
|
91
|
+
priority: 'MEDIUM',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: 'todo_task_3',
|
|
95
|
+
title: 'Collect testimonials',
|
|
96
|
+
description: "Ask last week's pilot crew for quotes.",
|
|
97
|
+
categoryId: homeCategoryId,
|
|
98
|
+
priority: 'LOW',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const task of tasks) {
|
|
103
|
+
await db.execute(
|
|
104
|
+
`INSERT INTO template_task (id, "projectId", "categoryId", title, description, completed, priority, tags)
|
|
105
105
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
[
|
|
107
|
+
task.id,
|
|
108
|
+
projectId,
|
|
109
|
+
task.categoryId,
|
|
110
|
+
task.title,
|
|
111
|
+
task.description,
|
|
112
|
+
0,
|
|
113
|
+
task.priority,
|
|
114
|
+
JSON.stringify(['demo']),
|
|
115
|
+
]
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
async function seedMessaging(params: { projectId: string; db: DatabasePort }) {
|
|
121
|
-
|
|
121
|
+
const { projectId, db } = params;
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
123
|
+
const existing = await db.query(
|
|
124
|
+
`SELECT COUNT(*) as count FROM template_conversation WHERE "projectId" = $1`,
|
|
125
|
+
[projectId]
|
|
126
|
+
);
|
|
127
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const conversationId = 'conv_demo_1';
|
|
130
|
+
const now = new Date().toISOString();
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
await db.execute(
|
|
133
|
+
`INSERT INTO template_conversation (id, "projectId", name, "isGroup", "avatarUrl", "updatedAt")
|
|
134
134
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
[conversationId, projectId, 'Team Chat', 1, null, now]
|
|
136
|
+
);
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
// Add participants
|
|
139
|
+
await db.execute(
|
|
140
|
+
`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
|
|
141
141
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
['part_1', conversationId, projectId, 'user_alice', 'Alice', 'member', now]
|
|
143
|
+
);
|
|
144
|
+
await db.execute(
|
|
145
|
+
`INSERT INTO template_conversation_participant (id, "conversationId", "projectId", "userId", "displayName", role, "joinedAt")
|
|
146
146
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
['part_2', conversationId, projectId, 'user_bob', 'Bob', 'member', now]
|
|
148
|
+
);
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
// Add messages
|
|
151
|
+
await db.execute(
|
|
152
|
+
`INSERT INTO template_message (id, "conversationId", "projectId", "senderId", "senderName", content, attachments, status, "createdAt", "updatedAt")
|
|
153
153
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
[
|
|
155
|
+
'msg_1',
|
|
156
|
+
conversationId,
|
|
157
|
+
projectId,
|
|
158
|
+
'user_alice',
|
|
159
|
+
'Alice',
|
|
160
|
+
'Hey team! Ready for the demo?',
|
|
161
|
+
JSON.stringify([]),
|
|
162
|
+
'DELIVERED',
|
|
163
|
+
now,
|
|
164
|
+
now,
|
|
165
|
+
]
|
|
166
|
+
);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
async function seedRecipes(params: { projectId: string; db: DatabasePort }) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
170
|
+
const { projectId, db } = params;
|
|
171
|
+
|
|
172
|
+
const existing = await db.query(
|
|
173
|
+
`SELECT COUNT(*) as count FROM template_recipe WHERE "projectId" = $1`,
|
|
174
|
+
[projectId]
|
|
175
|
+
);
|
|
176
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
177
|
+
|
|
178
|
+
// Add category
|
|
179
|
+
await db.execute(
|
|
180
|
+
`INSERT INTO template_recipe_category (id, "nameEn", "nameFr", icon) VALUES ($1, $2, $3, $4)`,
|
|
181
|
+
['cat_main', 'Main Courses', 'Plats Principaux', '🍽️']
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Add recipe
|
|
185
|
+
const recipeId = 'recipe_1';
|
|
186
|
+
await db.execute(
|
|
187
|
+
`INSERT INTO template_recipe (id, "projectId", "categoryId", "slugEn", "slugFr", "nameEn", "nameFr", "descriptionEn", "descriptionFr", "prepTimeMinutes", "cookTimeMinutes", servings, "isFavorite")
|
|
188
188
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
189
|
+
[
|
|
190
|
+
recipeId,
|
|
191
|
+
projectId,
|
|
192
|
+
'cat_main',
|
|
193
|
+
'grilled-salmon',
|
|
194
|
+
'saumon-grille',
|
|
195
|
+
'Grilled Salmon',
|
|
196
|
+
'Saumon Grillé',
|
|
197
|
+
'Delicious grilled salmon with lemon',
|
|
198
|
+
'Délicieux saumon grillé au citron',
|
|
199
|
+
10,
|
|
200
|
+
15,
|
|
201
|
+
4,
|
|
202
|
+
0,
|
|
203
|
+
]
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Add ingredients
|
|
207
|
+
await db.execute(
|
|
208
|
+
`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
|
|
209
209
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
['ing_1', recipeId, 'Salmon fillet', 'Filet de saumon', '4 pieces', 1]
|
|
211
|
+
);
|
|
212
|
+
await db.execute(
|
|
213
|
+
`INSERT INTO template_recipe_ingredient (id, "recipeId", "nameEn", "nameFr", quantity, ordering)
|
|
214
214
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
['ing_2', recipeId, 'Lemon', 'Citron', '1 whole', 2]
|
|
216
|
+
);
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
// Add instructions
|
|
219
|
+
await db.execute(
|
|
220
|
+
`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
|
|
221
221
|
VALUES ($1, $2, $3, $4, $5)`,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
222
|
+
[
|
|
223
|
+
'inst_1',
|
|
224
|
+
recipeId,
|
|
225
|
+
'Preheat grill to medium-high heat.',
|
|
226
|
+
'Préchauffer le grill à feu moyen-vif.',
|
|
227
|
+
1,
|
|
228
|
+
]
|
|
229
|
+
);
|
|
230
|
+
await db.execute(
|
|
231
|
+
`INSERT INTO template_recipe_instruction (id, "recipeId", "contentEn", "contentFr", ordering)
|
|
232
232
|
VALUES ($1, $2, $3, $4, $5)`,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
233
|
+
[
|
|
234
|
+
'inst_2',
|
|
235
|
+
recipeId,
|
|
236
|
+
'Season salmon and grill for 4-5 minutes per side.',
|
|
237
|
+
'Assaisonner le saumon et griller 4-5 minutes de chaque côté.',
|
|
238
|
+
2,
|
|
239
|
+
]
|
|
240
|
+
);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
async function seedCrmPipeline(params: {
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
projectId: string;
|
|
245
|
+
db: DatabasePort;
|
|
246
246
|
}) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
247
|
+
const { projectId, db } = params;
|
|
248
|
+
|
|
249
|
+
const existing = await db.query(
|
|
250
|
+
`SELECT COUNT(*) as count FROM crm_pipeline WHERE "projectId" = $1`,
|
|
251
|
+
[projectId]
|
|
252
|
+
);
|
|
253
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
254
|
+
|
|
255
|
+
const pipelineId = 'pipeline_sales';
|
|
256
|
+
await db.execute(
|
|
257
|
+
`INSERT INTO crm_pipeline (id, "projectId", name) VALUES ($1, $2, $3)`,
|
|
258
|
+
[pipelineId, projectId, 'Sales Pipeline']
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const stages = [
|
|
262
|
+
{ id: 'stage_lead', name: 'Lead', position: 1 },
|
|
263
|
+
{ id: 'stage_contact', name: 'Contact Made', position: 2 },
|
|
264
|
+
{ id: 'stage_proposal', name: 'Proposal', position: 3 },
|
|
265
|
+
{ id: 'stage_negotiation', name: 'Negotiation', position: 4 },
|
|
266
|
+
{ id: 'stage_closed', name: 'Closed', position: 5 },
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
for (const stage of stages) {
|
|
270
|
+
await db.execute(
|
|
271
|
+
`INSERT INTO crm_stage (id, "pipelineId", name, position) VALUES ($1, $2, $3, $4)`,
|
|
272
|
+
[stage.id, pipelineId, stage.name, stage.position]
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
async function seedSaasBoilerplate(params: {
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
projectId: string;
|
|
279
|
+
db: DatabasePort;
|
|
280
280
|
}) {
|
|
281
|
-
|
|
281
|
+
const { projectId, db } = params;
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
283
|
+
const existing = await db.query(
|
|
284
|
+
`SELECT COUNT(*) as count FROM saas_project WHERE "projectId" = $1`,
|
|
285
|
+
[projectId]
|
|
286
|
+
);
|
|
287
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
289
|
+
await db.execute(
|
|
290
|
+
`INSERT INTO saas_project (id, "projectId", "organizationId", name, description, status, tier)
|
|
291
291
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
[
|
|
293
|
+
'saas_proj_1',
|
|
294
|
+
projectId,
|
|
295
|
+
'org_demo',
|
|
296
|
+
'Demo Project',
|
|
297
|
+
'A demo SaaS project',
|
|
298
|
+
'ACTIVE',
|
|
299
|
+
'PRO',
|
|
300
|
+
]
|
|
301
|
+
);
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
async function seedAgentConsole(params: {
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
projectId: string;
|
|
306
|
+
db: DatabasePort;
|
|
307
307
|
}) {
|
|
308
|
-
|
|
308
|
+
const { projectId, db } = params;
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
310
|
+
const existing = await db.query(
|
|
311
|
+
`SELECT COUNT(*) as count FROM agent_definition WHERE "projectId" = $1`,
|
|
312
|
+
[projectId]
|
|
313
|
+
);
|
|
314
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
await db.execute(
|
|
317
|
+
`INSERT INTO agent_definition (id, "projectId", "organizationId", name, description, "modelProvider", "modelName", status)
|
|
318
318
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
319
|
+
[
|
|
320
|
+
'agent_1',
|
|
321
|
+
projectId,
|
|
322
|
+
'org_demo',
|
|
323
|
+
'Demo Agent',
|
|
324
|
+
'A demo AI agent',
|
|
325
|
+
'openai',
|
|
326
|
+
'gpt-4',
|
|
327
|
+
'ACTIVE',
|
|
328
|
+
]
|
|
329
|
+
);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
async function seedWorkflowSystem(params: {
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
projectId: string;
|
|
334
|
+
db: DatabasePort;
|
|
335
335
|
}) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
336
|
+
const { projectId, db } = params;
|
|
337
|
+
|
|
338
|
+
const existing = await db.query(
|
|
339
|
+
`SELECT COUNT(*) as count FROM workflow_definition WHERE "projectId" = $1`,
|
|
340
|
+
[projectId]
|
|
341
|
+
);
|
|
342
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
343
|
+
|
|
344
|
+
const definitions = [
|
|
345
|
+
{
|
|
346
|
+
id: 'wf_expense',
|
|
347
|
+
name: 'Expense Approval',
|
|
348
|
+
description: 'Approve non-trivial spend before finance releases budget.',
|
|
349
|
+
type: 'APPROVAL',
|
|
350
|
+
status: 'ACTIVE',
|
|
351
|
+
createdAt: '2026-03-10T09:00:00.000Z',
|
|
352
|
+
updatedAt: '2026-03-20T08:15:00.000Z',
|
|
353
|
+
steps: [
|
|
354
|
+
{
|
|
355
|
+
id: 'wfstep_expense_manager',
|
|
356
|
+
name: 'Manager Review',
|
|
357
|
+
description: 'Validate business value and team budget.',
|
|
358
|
+
stepOrder: 1,
|
|
359
|
+
type: 'APPROVAL',
|
|
360
|
+
requiredRoles: ['manager'],
|
|
361
|
+
createdAt: '2026-03-10T09:00:00.000Z',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: 'wfstep_expense_finance',
|
|
365
|
+
name: 'Finance Review',
|
|
366
|
+
description: 'Confirm ledger coding and spending threshold.',
|
|
367
|
+
stepOrder: 2,
|
|
368
|
+
type: 'APPROVAL',
|
|
369
|
+
requiredRoles: ['finance'],
|
|
370
|
+
createdAt: '2026-03-10T09:10:00.000Z',
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: 'wf_vendor',
|
|
376
|
+
name: 'Vendor Onboarding',
|
|
377
|
+
description:
|
|
378
|
+
'Sequence security, procurement, and legal before activation.',
|
|
379
|
+
type: 'SEQUENTIAL',
|
|
380
|
+
status: 'ACTIVE',
|
|
381
|
+
createdAt: '2026-03-08T11:00:00.000Z',
|
|
382
|
+
updatedAt: '2026-03-19T13:10:00.000Z',
|
|
383
|
+
steps: [
|
|
384
|
+
{
|
|
385
|
+
id: 'wfstep_vendor_security',
|
|
386
|
+
name: 'Security Check',
|
|
387
|
+
description: 'Review data access and integration scope.',
|
|
388
|
+
stepOrder: 1,
|
|
389
|
+
type: 'APPROVAL',
|
|
390
|
+
requiredRoles: ['security'],
|
|
391
|
+
createdAt: '2026-03-08T11:00:00.000Z',
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: 'wfstep_vendor_procurement',
|
|
395
|
+
name: 'Procurement Check',
|
|
396
|
+
description: 'Validate pricing, procurement policy, and owner.',
|
|
397
|
+
stepOrder: 2,
|
|
398
|
+
type: 'APPROVAL',
|
|
399
|
+
requiredRoles: ['procurement'],
|
|
400
|
+
createdAt: '2026-03-08T11:05:00.000Z',
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: 'wfstep_vendor_legal',
|
|
404
|
+
name: 'Legal Sign-off',
|
|
405
|
+
description: 'Approve terms before the vendor goes live.',
|
|
406
|
+
stepOrder: 3,
|
|
407
|
+
type: 'APPROVAL',
|
|
408
|
+
requiredRoles: ['legal'],
|
|
409
|
+
createdAt: '2026-03-08T11:10:00.000Z',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'wf_policy_exception',
|
|
415
|
+
name: 'Policy Exception',
|
|
416
|
+
description:
|
|
417
|
+
'Escalate a temporary exception through team lead and compliance.',
|
|
418
|
+
type: 'APPROVAL',
|
|
419
|
+
status: 'DRAFT',
|
|
420
|
+
createdAt: '2026-03-15T07:30:00.000Z',
|
|
421
|
+
updatedAt: '2026-03-18T11:20:00.000Z',
|
|
422
|
+
steps: [
|
|
423
|
+
{
|
|
424
|
+
id: 'wfstep_policy_lead',
|
|
425
|
+
name: 'Team Lead Review',
|
|
426
|
+
description: 'Check urgency and expected blast radius.',
|
|
427
|
+
stepOrder: 1,
|
|
428
|
+
type: 'APPROVAL',
|
|
429
|
+
requiredRoles: ['team-lead'],
|
|
430
|
+
createdAt: '2026-03-15T07:30:00.000Z',
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
id: 'wfstep_policy_compliance',
|
|
434
|
+
name: 'Compliance Review',
|
|
435
|
+
description: 'Accept or reject the exception request.',
|
|
436
|
+
stepOrder: 2,
|
|
437
|
+
type: 'APPROVAL',
|
|
438
|
+
requiredRoles: ['compliance'],
|
|
439
|
+
createdAt: '2026-03-15T07:40:00.000Z',
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
] as const;
|
|
444
|
+
|
|
445
|
+
const instances = [
|
|
446
|
+
{
|
|
447
|
+
id: 'wfinst_expense_open',
|
|
448
|
+
definitionId: 'wf_expense',
|
|
449
|
+
status: 'IN_PROGRESS',
|
|
450
|
+
currentStepId: 'wfstep_expense_finance',
|
|
451
|
+
data: { amount: 4200, currency: 'EUR', vendor: 'Nimbus AI' },
|
|
452
|
+
requestedBy: 'sarah@contractspec.io',
|
|
453
|
+
startedAt: '2026-03-20T08:00:00.000Z',
|
|
454
|
+
completedAt: null,
|
|
455
|
+
approvals: [
|
|
456
|
+
{
|
|
457
|
+
id: 'wfappr_expense_manager',
|
|
458
|
+
stepId: 'wfstep_expense_manager',
|
|
459
|
+
status: 'APPROVED',
|
|
460
|
+
actorId: 'manager.demo',
|
|
461
|
+
comment: 'Approved for the Q2 automation budget.',
|
|
462
|
+
decidedAt: '2026-03-20T08:15:00.000Z',
|
|
463
|
+
createdAt: '2026-03-20T08:05:00.000Z',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: 'wfappr_expense_finance',
|
|
467
|
+
stepId: 'wfstep_expense_finance',
|
|
468
|
+
status: 'PENDING',
|
|
469
|
+
actorId: null,
|
|
470
|
+
comment: null,
|
|
471
|
+
decidedAt: null,
|
|
472
|
+
createdAt: '2026-03-20T08:15:00.000Z',
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: 'wfinst_vendor_done',
|
|
478
|
+
definitionId: 'wf_vendor',
|
|
479
|
+
status: 'COMPLETED',
|
|
480
|
+
currentStepId: null,
|
|
481
|
+
data: { vendor: 'Acme Cloud', riskTier: 'medium' },
|
|
482
|
+
requestedBy: 'leo@contractspec.io',
|
|
483
|
+
startedAt: '2026-03-19T09:30:00.000Z',
|
|
484
|
+
completedAt: '2026-03-19T13:10:00.000Z',
|
|
485
|
+
approvals: [
|
|
486
|
+
{
|
|
487
|
+
id: 'wfappr_vendor_security',
|
|
488
|
+
stepId: 'wfstep_vendor_security',
|
|
489
|
+
status: 'APPROVED',
|
|
490
|
+
actorId: 'security.demo',
|
|
491
|
+
comment: 'SOC2 scope is acceptable.',
|
|
492
|
+
decidedAt: '2026-03-19T10:10:00.000Z',
|
|
493
|
+
createdAt: '2026-03-19T09:35:00.000Z',
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 'wfappr_vendor_procurement',
|
|
497
|
+
stepId: 'wfstep_vendor_procurement',
|
|
498
|
+
status: 'APPROVED',
|
|
499
|
+
actorId: 'procurement.demo',
|
|
500
|
+
comment: 'Commercial terms match the preferred vendor tier.',
|
|
501
|
+
decidedAt: '2026-03-19T11:25:00.000Z',
|
|
502
|
+
createdAt: '2026-03-19T10:15:00.000Z',
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: 'wfappr_vendor_legal',
|
|
506
|
+
stepId: 'wfstep_vendor_legal',
|
|
507
|
+
status: 'APPROVED',
|
|
508
|
+
actorId: 'legal.demo',
|
|
509
|
+
comment: 'MSA redlines are complete.',
|
|
510
|
+
decidedAt: '2026-03-19T13:05:00.000Z',
|
|
511
|
+
createdAt: '2026-03-19T11:30:00.000Z',
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: 'wfinst_policy_rejected',
|
|
517
|
+
definitionId: 'wf_policy_exception',
|
|
518
|
+
status: 'REJECTED',
|
|
519
|
+
currentStepId: 'wfstep_policy_compliance',
|
|
520
|
+
data: { policy: 'Model rollout freeze', durationDays: 14 },
|
|
521
|
+
requestedBy: 'maya@contractspec.io',
|
|
522
|
+
startedAt: '2026-03-18T10:00:00.000Z',
|
|
523
|
+
completedAt: '2026-03-18T11:20:00.000Z',
|
|
524
|
+
approvals: [
|
|
525
|
+
{
|
|
526
|
+
id: 'wfappr_policy_lead',
|
|
527
|
+
stepId: 'wfstep_policy_lead',
|
|
528
|
+
status: 'APPROVED',
|
|
529
|
+
actorId: 'lead.demo',
|
|
530
|
+
comment: 'Escalation justified for the release train.',
|
|
531
|
+
decidedAt: '2026-03-18T10:30:00.000Z',
|
|
532
|
+
createdAt: '2026-03-18T10:05:00.000Z',
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: 'wfappr_policy_compliance',
|
|
536
|
+
stepId: 'wfstep_policy_compliance',
|
|
537
|
+
status: 'REJECTED',
|
|
538
|
+
actorId: 'compliance.demo',
|
|
539
|
+
comment: 'Exception exceeds the allowed blast radius.',
|
|
540
|
+
decidedAt: '2026-03-18T11:15:00.000Z',
|
|
541
|
+
createdAt: '2026-03-18T10:35:00.000Z',
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
] as const;
|
|
546
|
+
|
|
547
|
+
for (const definition of definitions) {
|
|
548
|
+
await db.execute(
|
|
549
|
+
`INSERT INTO workflow_definition (id, "projectId", "organizationId", name, description, type, status, "createdAt", "updatedAt")
|
|
550
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
551
|
+
[
|
|
552
|
+
definition.id,
|
|
553
|
+
projectId,
|
|
554
|
+
'org_demo',
|
|
555
|
+
definition.name,
|
|
556
|
+
definition.description,
|
|
557
|
+
definition.type,
|
|
558
|
+
definition.status,
|
|
559
|
+
definition.createdAt,
|
|
560
|
+
definition.updatedAt,
|
|
561
|
+
]
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
for (const step of definition.steps) {
|
|
565
|
+
await db.execute(
|
|
566
|
+
`INSERT INTO workflow_step (id, "definitionId", name, description, "stepOrder", type, "requiredRoles", "createdAt")
|
|
567
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
568
|
+
[
|
|
569
|
+
step.id,
|
|
570
|
+
definition.id,
|
|
571
|
+
step.name,
|
|
572
|
+
step.description,
|
|
573
|
+
step.stepOrder,
|
|
574
|
+
step.type,
|
|
575
|
+
JSON.stringify(step.requiredRoles),
|
|
576
|
+
step.createdAt,
|
|
577
|
+
]
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
for (const instance of instances) {
|
|
583
|
+
await db.execute(
|
|
584
|
+
`INSERT INTO workflow_instance (id, "projectId", "definitionId", status, "currentStepId", data, "requestedBy", "startedAt", "completedAt")
|
|
585
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
|
586
|
+
[
|
|
587
|
+
instance.id,
|
|
588
|
+
projectId,
|
|
589
|
+
instance.definitionId,
|
|
590
|
+
instance.status,
|
|
591
|
+
instance.currentStepId,
|
|
592
|
+
JSON.stringify(instance.data),
|
|
593
|
+
instance.requestedBy,
|
|
594
|
+
instance.startedAt,
|
|
595
|
+
instance.completedAt,
|
|
596
|
+
]
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
for (const approval of instance.approvals) {
|
|
600
|
+
await db.execute(
|
|
601
|
+
`INSERT INTO workflow_approval (id, "instanceId", "stepId", status, "actorId", comment, "decidedAt", "createdAt")
|
|
602
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
|
|
603
|
+
[
|
|
604
|
+
approval.id,
|
|
605
|
+
instance.id,
|
|
606
|
+
approval.stepId,
|
|
607
|
+
approval.status,
|
|
608
|
+
approval.actorId,
|
|
609
|
+
approval.comment,
|
|
610
|
+
approval.decidedAt,
|
|
611
|
+
approval.createdAt,
|
|
612
|
+
]
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
357
616
|
}
|
|
358
617
|
|
|
359
618
|
async function seedMarketplace(params: {
|
|
360
|
-
|
|
361
|
-
|
|
619
|
+
projectId: string;
|
|
620
|
+
db: DatabasePort;
|
|
362
621
|
}) {
|
|
363
|
-
|
|
622
|
+
const { projectId, db } = params;
|
|
364
623
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
624
|
+
const existing = await db.query(
|
|
625
|
+
`SELECT COUNT(*) as count FROM marketplace_store WHERE "projectId" = $1`,
|
|
626
|
+
[projectId]
|
|
627
|
+
);
|
|
628
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
370
629
|
|
|
371
|
-
|
|
372
|
-
|
|
630
|
+
await db.execute(
|
|
631
|
+
`INSERT INTO marketplace_store (id, "projectId", "organizationId", name, description, status)
|
|
373
632
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
374
|
-
|
|
375
|
-
|
|
633
|
+
['store_1', projectId, 'org_demo', 'Demo Store', 'A demo store', 'ACTIVE']
|
|
634
|
+
);
|
|
376
635
|
}
|
|
377
636
|
|
|
378
637
|
async function seedIntegrationHub(params: {
|
|
379
|
-
|
|
380
|
-
|
|
638
|
+
projectId: string;
|
|
639
|
+
db: DatabasePort;
|
|
381
640
|
}) {
|
|
382
|
-
|
|
641
|
+
const { projectId, db } = params;
|
|
383
642
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
643
|
+
const existing = await db.query(
|
|
644
|
+
`SELECT COUNT(*) as count FROM integration WHERE "projectId" = $1`,
|
|
645
|
+
[projectId]
|
|
646
|
+
);
|
|
647
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
389
648
|
|
|
390
|
-
|
|
391
|
-
|
|
649
|
+
await db.execute(
|
|
650
|
+
`INSERT INTO integration (id, "projectId", "organizationId", name, description, type, status)
|
|
392
651
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
652
|
+
[
|
|
653
|
+
'int_1',
|
|
654
|
+
projectId,
|
|
655
|
+
'org_demo',
|
|
656
|
+
'Salesforce',
|
|
657
|
+
'Salesforce CRM integration',
|
|
658
|
+
'CRM',
|
|
659
|
+
'ACTIVE',
|
|
660
|
+
]
|
|
661
|
+
);
|
|
403
662
|
}
|
|
404
663
|
|
|
405
664
|
async function seedAnalyticsDashboard(params: {
|
|
406
|
-
|
|
407
|
-
|
|
665
|
+
projectId: string;
|
|
666
|
+
db: DatabasePort;
|
|
408
667
|
}) {
|
|
409
|
-
|
|
668
|
+
const { projectId, db } = params;
|
|
410
669
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
670
|
+
const existing = await db.query(
|
|
671
|
+
`SELECT COUNT(*) as count FROM analytics_dashboard WHERE "projectId" = $1`,
|
|
672
|
+
[projectId]
|
|
673
|
+
);
|
|
674
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
416
675
|
|
|
417
|
-
|
|
418
|
-
|
|
676
|
+
await db.execute(
|
|
677
|
+
`INSERT INTO analytics_dashboard (id, "projectId", "organizationId", name, slug, description, status)
|
|
419
678
|
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
679
|
+
[
|
|
680
|
+
'dash_1',
|
|
681
|
+
projectId,
|
|
682
|
+
'org_demo',
|
|
683
|
+
'Sales Overview',
|
|
684
|
+
'sales-overview',
|
|
685
|
+
'Sales performance dashboard',
|
|
686
|
+
'PUBLISHED',
|
|
687
|
+
]
|
|
688
|
+
);
|
|
430
689
|
}
|
|
431
690
|
|
|
432
691
|
async function seedPolicyKnowledgeAssistant(params: {
|
|
433
|
-
|
|
434
|
-
|
|
692
|
+
projectId: string;
|
|
693
|
+
db: DatabasePort;
|
|
435
694
|
}) {
|
|
436
|
-
|
|
695
|
+
const { projectId, db } = params;
|
|
437
696
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
697
|
+
const existing = await db.query(
|
|
698
|
+
`SELECT COUNT(*) as count FROM psa_user_context WHERE "projectId" = $1`,
|
|
699
|
+
[projectId]
|
|
700
|
+
);
|
|
701
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
443
702
|
|
|
444
|
-
|
|
445
|
-
|
|
703
|
+
await db.execute(
|
|
704
|
+
`INSERT INTO psa_user_context ("projectId", locale, jurisdiction, "allowedScope")
|
|
446
705
|
VALUES ($1, $2, $3, $4)`,
|
|
447
|
-
|
|
448
|
-
|
|
706
|
+
[projectId, 'en-GB', 'EU', 'education_only']
|
|
707
|
+
);
|
|
449
708
|
}
|