@zauso-ai/capstan-packs-core 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1574 @@
1
+ const PACKS_APPLIED = Symbol.for("capstan.graph-packs.applied");
2
+ const authPack = defineGraphPack({
3
+ key: "auth",
4
+ title: "Authentication Pack",
5
+ description: "Adds user, role, and invitation primitives for operator-facing applications.",
6
+ apply(context) {
7
+ const subjectName = readOptionString(context.selection, "subjectName") ?? "User";
8
+ const subjectPlural = readOptionString(context.selection, "subjectPlural") ?? pluralize(subjectName);
9
+ const subjectKeyStem = toPackPascalName(subjectName);
10
+ const subjectPluralKeyStem = toPackPascalName(subjectPlural);
11
+ const userResourceKey = readOptionString(context.selection, "userResourceKey") ?? "user";
12
+ const roleResourceKey = readOptionString(context.selection, "roleResourceKey") ?? "role";
13
+ const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${subjectPluralKeyStem}`;
14
+ const inviteCapabilityKey = readOptionString(context.selection, "inviteCapabilityKey") ?? `invite${subjectKeyStem}`;
15
+ const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${userResourceKey}List`;
16
+ const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${userResourceKey}Form`;
17
+ return {
18
+ resources: [
19
+ {
20
+ key: userResourceKey,
21
+ title: subjectName,
22
+ description: `An authenticated ${subjectName.toLowerCase()} who can operate the application.`,
23
+ fields: {
24
+ email: {
25
+ type: "string",
26
+ required: true
27
+ },
28
+ displayName: {
29
+ type: "string",
30
+ required: true
31
+ },
32
+ status: {
33
+ type: "string",
34
+ required: true,
35
+ constraints: {
36
+ enum: ["active", "invited", "disabled"]
37
+ }
38
+ }
39
+ }
40
+ },
41
+ {
42
+ key: roleResourceKey,
43
+ title: "Role",
44
+ description: "An authorization role assignable to authenticated operators.",
45
+ fields: {
46
+ name: {
47
+ type: "string",
48
+ required: true
49
+ },
50
+ description: {
51
+ type: "string"
52
+ }
53
+ }
54
+ }
55
+ ],
56
+ capabilities: [
57
+ {
58
+ key: listCapabilityKey,
59
+ title: `List ${subjectPlural}`,
60
+ description: `Read the current ${subjectPlural.toLowerCase()} in the system.`,
61
+ mode: "read",
62
+ resources: [userResourceKey],
63
+ policy: "authenticated"
64
+ },
65
+ {
66
+ key: inviteCapabilityKey,
67
+ title: `Invite ${subjectName}`,
68
+ description: `Invite a new ${subjectName.toLowerCase()} and assign an initial role.`,
69
+ mode: "write",
70
+ resources: [userResourceKey, roleResourceKey],
71
+ policy: "authenticated",
72
+ input: {
73
+ email: {
74
+ type: "string",
75
+ required: true
76
+ },
77
+ roleKey: {
78
+ type: "string",
79
+ required: true
80
+ }
81
+ }
82
+ }
83
+ ],
84
+ policies: [
85
+ {
86
+ key: "authenticated",
87
+ title: "Authenticated Access",
88
+ description: "Allows access only for authenticated operators.",
89
+ effect: "allow"
90
+ }
91
+ ],
92
+ views: [
93
+ {
94
+ key: listViewKey,
95
+ title: subjectPlural,
96
+ kind: "list",
97
+ resource: userResourceKey,
98
+ capability: listCapabilityKey
99
+ },
100
+ {
101
+ key: formViewKey,
102
+ title: `Invite ${subjectName}`,
103
+ kind: "form",
104
+ resource: userResourceKey,
105
+ capability: inviteCapabilityKey
106
+ }
107
+ ]
108
+ };
109
+ }
110
+ });
111
+ const tenantPack = defineGraphPack({
112
+ key: "tenant",
113
+ title: "Organization And Tenant Pack",
114
+ description: "Adds multi-tenant organization and membership primitives.",
115
+ dependsOn: ["auth"],
116
+ apply(context) {
117
+ const entityName = readOptionString(context.selection, "entityName") ?? "Organization";
118
+ const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
119
+ const entityKeyStem = toPackPascalName(entityName);
120
+ const entityPluralKeyStem = toPackPascalName(entityPlural);
121
+ const entityResourceKey = readOptionString(context.selection, "entityResourceKey") ?? toPackKey(entityName);
122
+ const membershipResourceKey = readOptionString(context.selection, "membershipResourceKey") ?? "membership";
123
+ const listEntitiesCapabilityKey = readOptionString(context.selection, "listEntitiesCapabilityKey") ??
124
+ `list${entityPluralKeyStem}`;
125
+ const provisionEntityCapabilityKey = readOptionString(context.selection, "provisionEntityCapabilityKey") ??
126
+ `provision${entityKeyStem}`;
127
+ const listMembershipsCapabilityKey = readOptionString(context.selection, "listMembershipsCapabilityKey") ?? "listMemberships";
128
+ const entityListViewKey = readOptionString(context.selection, "entityListViewKey") ?? `${entityResourceKey}List`;
129
+ const membershipListViewKey = readOptionString(context.selection, "membershipListViewKey") ?? `${membershipResourceKey}List`;
130
+ return {
131
+ resources: [
132
+ {
133
+ key: entityResourceKey,
134
+ title: entityName,
135
+ description: `A tenant container that scopes work inside the application.`,
136
+ fields: {
137
+ name: {
138
+ type: "string",
139
+ required: true
140
+ },
141
+ slug: {
142
+ type: "string",
143
+ required: true
144
+ },
145
+ status: {
146
+ type: "string",
147
+ required: true,
148
+ constraints: {
149
+ enum: ["active", "suspended"]
150
+ }
151
+ }
152
+ }
153
+ },
154
+ {
155
+ key: membershipResourceKey,
156
+ title: `${entityName} Membership`,
157
+ description: `A link between one user and one ${entityName.toLowerCase()}.`,
158
+ fields: {
159
+ userId: {
160
+ type: "string",
161
+ required: true
162
+ },
163
+ [`${entityResourceKey}Id`]: {
164
+ type: "string",
165
+ required: true
166
+ },
167
+ roleKey: {
168
+ type: "string",
169
+ required: true
170
+ }
171
+ }
172
+ }
173
+ ],
174
+ capabilities: [
175
+ {
176
+ key: listEntitiesCapabilityKey,
177
+ title: `List ${entityPlural}`,
178
+ description: `Read the ${entityPlural.toLowerCase()} that scope work inside the application.`,
179
+ mode: "read",
180
+ resources: [entityResourceKey],
181
+ policy: "tenantScoped"
182
+ },
183
+ {
184
+ key: provisionEntityCapabilityKey,
185
+ title: `Provision ${entityName}`,
186
+ description: `Create a new ${entityName.toLowerCase()} in a controlled way.`,
187
+ mode: "write",
188
+ resources: [entityResourceKey],
189
+ policy: "tenantScoped",
190
+ input: {
191
+ name: {
192
+ type: "string",
193
+ required: true
194
+ },
195
+ slug: {
196
+ type: "string",
197
+ required: true
198
+ }
199
+ }
200
+ },
201
+ {
202
+ key: listMembershipsCapabilityKey,
203
+ title: "List Memberships",
204
+ description: `Read membership assignments for ${entityPlural.toLowerCase()}.`,
205
+ mode: "read",
206
+ resources: [membershipResourceKey, entityResourceKey, "user"],
207
+ policy: "tenantScoped"
208
+ }
209
+ ],
210
+ policies: [
211
+ {
212
+ key: "tenantScoped",
213
+ title: "Tenant Scoped Access",
214
+ description: "Allows access only when a request is correctly scoped to one tenant.",
215
+ effect: "allow"
216
+ }
217
+ ],
218
+ views: [
219
+ {
220
+ key: entityListViewKey,
221
+ title: entityPlural,
222
+ kind: "list",
223
+ resource: entityResourceKey,
224
+ capability: listEntitiesCapabilityKey
225
+ },
226
+ {
227
+ key: membershipListViewKey,
228
+ title: "Memberships",
229
+ kind: "list",
230
+ resource: membershipResourceKey,
231
+ capability: listMembershipsCapabilityKey
232
+ }
233
+ ]
234
+ };
235
+ }
236
+ });
237
+ const workflowPack = defineGraphPack({
238
+ key: "workflow",
239
+ title: "Workflow Pack",
240
+ description: "Adds durable workflow requests, approval semantics, and generated artifacts.",
241
+ dependsOn: ["auth"],
242
+ apply(context) {
243
+ const entityName = readOptionString(context.selection, "entityName") ?? "Work Request";
244
+ const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
245
+ const entityKeyStem = toPackPascalName(entityName);
246
+ const entityPluralKeyStem = toPackPascalName(entityPlural);
247
+ const resourceKey = readOptionString(context.selection, "resourceKey") ?? toPackKey(entityName);
248
+ const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${entityPluralKeyStem}`;
249
+ const submitCapabilityKey = readOptionString(context.selection, "submitCapabilityKey") ?? `submit${entityKeyStem}`;
250
+ const processCapabilityKey = readOptionString(context.selection, "processCapabilityKey") ?? `process${entityKeyStem}`;
251
+ const taskKey = readOptionString(context.selection, "taskKey") ?? `${processCapabilityKey}Task`;
252
+ const artifactKey = readOptionString(context.selection, "artifactKey") ?? `${resourceKey}Report`;
253
+ const approvalPolicyKey = readOptionString(context.selection, "approvalPolicyKey") ?? "workflowApprovalRequired";
254
+ const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${resourceKey}List`;
255
+ const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${resourceKey}Form`;
256
+ const detailViewKey = readOptionString(context.selection, "detailViewKey") ?? `${resourceKey}Detail`;
257
+ const accessPolicyKey = getAccessPolicyKey(context);
258
+ const durableRuntime = createDurableRuntimeFragments({
259
+ taskKey,
260
+ taskTitle: `Process ${entityName} Task`,
261
+ taskDescription: `Durably processes one ${entityName.toLowerCase()} through review and completion.`,
262
+ artifactKey,
263
+ artifactTitle: `${entityName} Report`,
264
+ artifactDescription: `A generated report artifact produced when one ${entityName.toLowerCase()} finishes processing.`,
265
+ artifactKind: "report",
266
+ approvalPolicyKey,
267
+ approvalTitle: `${entityName} Approval Required`,
268
+ approvalDescription: `Requires explicit approval before ${entityName.toLowerCase()} processing may continue.`
269
+ });
270
+ return {
271
+ resources: [
272
+ {
273
+ key: resourceKey,
274
+ title: entityName,
275
+ description: `A durable workflow request that can be reviewed and completed over time.`,
276
+ fields: {
277
+ title: {
278
+ type: "string",
279
+ required: true
280
+ },
281
+ summary: {
282
+ type: "string"
283
+ },
284
+ status: {
285
+ type: "string",
286
+ required: true,
287
+ constraints: {
288
+ enum: ["draft", "submitted", "in_review", "completed", "blocked"]
289
+ }
290
+ },
291
+ requestedById: {
292
+ type: "string",
293
+ required: true
294
+ }
295
+ }
296
+ }
297
+ ],
298
+ capabilities: [
299
+ {
300
+ key: listCapabilityKey,
301
+ title: `List ${entityPlural}`,
302
+ description: `Read the ${entityPlural.toLowerCase()} currently tracked by the workflow system.`,
303
+ mode: "read",
304
+ resources: [resourceKey],
305
+ policy: accessPolicyKey
306
+ },
307
+ {
308
+ key: submitCapabilityKey,
309
+ title: `Submit ${entityName}`,
310
+ description: `Create and submit a new ${entityName.toLowerCase()} for review.`,
311
+ mode: "write",
312
+ resources: [resourceKey],
313
+ policy: accessPolicyKey,
314
+ input: {
315
+ title: {
316
+ type: "string",
317
+ required: true
318
+ },
319
+ summary: {
320
+ type: "string"
321
+ }
322
+ }
323
+ },
324
+ {
325
+ key: processCapabilityKey,
326
+ title: `Process ${entityName}`,
327
+ description: `Advance one ${entityName.toLowerCase()} through a durable review workflow.`,
328
+ mode: "external",
329
+ resources: [resourceKey],
330
+ policy: approvalPolicyKey,
331
+ task: taskKey,
332
+ input: {
333
+ [`${resourceKey}Id`]: {
334
+ type: "string",
335
+ required: true
336
+ }
337
+ }
338
+ }
339
+ ],
340
+ tasks: durableRuntime.tasks,
341
+ policies: durableRuntime.policies,
342
+ artifacts: durableRuntime.artifacts,
343
+ views: [
344
+ {
345
+ key: listViewKey,
346
+ title: entityPlural,
347
+ kind: "list",
348
+ resource: resourceKey,
349
+ capability: listCapabilityKey
350
+ },
351
+ {
352
+ key: formViewKey,
353
+ title: `Submit ${entityName}`,
354
+ kind: "form",
355
+ resource: resourceKey,
356
+ capability: submitCapabilityKey
357
+ },
358
+ {
359
+ key: detailViewKey,
360
+ title: `${entityName} Detail`,
361
+ kind: "detail",
362
+ resource: resourceKey,
363
+ capability: processCapabilityKey
364
+ }
365
+ ]
366
+ };
367
+ }
368
+ });
369
+ const connectorPack = defineGraphPack({
370
+ key: "connector",
371
+ title: "Connector Pack",
372
+ description: "Adds external system connectors, durable sync tasks, and sync artifacts.",
373
+ dependsOn: ["tenant"],
374
+ apply(context) {
375
+ const entityName = readOptionString(context.selection, "entityName") ?? "Connector";
376
+ const entityPlural = readOptionString(context.selection, "entityPlural") ?? pluralize(entityName);
377
+ const entityKeyStem = toPackPascalName(entityName);
378
+ const entityPluralKeyStem = toPackPascalName(entityPlural);
379
+ const resourceKey = readOptionString(context.selection, "resourceKey") ?? toPackKey(entityName);
380
+ const listCapabilityKey = readOptionString(context.selection, "listCapabilityKey") ?? `list${entityPluralKeyStem}`;
381
+ const configureCapabilityKey = readOptionString(context.selection, "configureCapabilityKey") ??
382
+ `configure${entityKeyStem}`;
383
+ const syncCapabilityKey = readOptionString(context.selection, "syncCapabilityKey") ?? `sync${entityKeyStem}`;
384
+ const taskKey = readOptionString(context.selection, "taskKey") ?? `${syncCapabilityKey}Task`;
385
+ const artifactKey = readOptionString(context.selection, "artifactKey") ?? `${resourceKey}SyncReport`;
386
+ const approvalPolicyKey = readOptionString(context.selection, "approvalPolicyKey") ?? "connectorSyncApprovalRequired";
387
+ const listViewKey = readOptionString(context.selection, "listViewKey") ?? `${resourceKey}List`;
388
+ const formViewKey = readOptionString(context.selection, "formViewKey") ?? `${resourceKey}Form`;
389
+ const detailViewKey = readOptionString(context.selection, "detailViewKey") ?? `${resourceKey}Detail`;
390
+ const accessPolicyKey = getAccessPolicyKey(context);
391
+ const durableRuntime = createDurableRuntimeFragments({
392
+ taskKey,
393
+ taskTitle: `Sync ${entityName} Task`,
394
+ taskDescription: `Durably synchronizes one ${entityName.toLowerCase()} and records the result.`,
395
+ artifactKey,
396
+ artifactTitle: `${entityName} Sync Report`,
397
+ artifactDescription: `A generated sync report artifact produced after one ${entityName.toLowerCase()} sync completes.`,
398
+ artifactKind: "report",
399
+ approvalPolicyKey,
400
+ approvalTitle: `${entityName} Sync Approval Required`,
401
+ approvalDescription: `Requires approval before ${entityName.toLowerCase()} sync may continue.`
402
+ });
403
+ return {
404
+ resources: [
405
+ {
406
+ key: resourceKey,
407
+ title: entityName,
408
+ description: `A configured external ${entityName.toLowerCase()} that can sync data into the application.`,
409
+ fields: {
410
+ name: {
411
+ type: "string",
412
+ required: true
413
+ },
414
+ provider: {
415
+ type: "string",
416
+ required: true
417
+ },
418
+ status: {
419
+ type: "string",
420
+ required: true,
421
+ constraints: {
422
+ enum: ["connected", "degraded", "disconnected"]
423
+ }
424
+ },
425
+ lastSyncedAt: {
426
+ type: "datetime"
427
+ }
428
+ }
429
+ }
430
+ ],
431
+ capabilities: [
432
+ {
433
+ key: listCapabilityKey,
434
+ title: `List ${entityPlural}`,
435
+ description: `Read the configured ${entityPlural.toLowerCase()} available to this application.`,
436
+ mode: "read",
437
+ resources: [resourceKey],
438
+ policy: accessPolicyKey
439
+ },
440
+ {
441
+ key: configureCapabilityKey,
442
+ title: `Configure ${entityName}`,
443
+ description: `Create or update a configured ${entityName.toLowerCase()} for one tenant.`,
444
+ mode: "write",
445
+ resources: [resourceKey],
446
+ policy: accessPolicyKey,
447
+ input: {
448
+ name: {
449
+ type: "string",
450
+ required: true
451
+ },
452
+ provider: {
453
+ type: "string",
454
+ required: true
455
+ },
456
+ secretReference: {
457
+ type: "string"
458
+ }
459
+ }
460
+ },
461
+ {
462
+ key: syncCapabilityKey,
463
+ title: `Sync ${entityName}`,
464
+ description: `Trigger a durable sync for one configured ${entityName.toLowerCase()}.`,
465
+ mode: "external",
466
+ resources: [resourceKey],
467
+ policy: approvalPolicyKey,
468
+ task: taskKey,
469
+ input: {
470
+ [`${resourceKey}Id`]: {
471
+ type: "string",
472
+ required: true
473
+ }
474
+ }
475
+ }
476
+ ],
477
+ tasks: durableRuntime.tasks,
478
+ policies: durableRuntime.policies,
479
+ artifacts: durableRuntime.artifacts,
480
+ views: [
481
+ {
482
+ key: listViewKey,
483
+ title: entityPlural,
484
+ kind: "list",
485
+ resource: resourceKey,
486
+ capability: listCapabilityKey
487
+ },
488
+ {
489
+ key: formViewKey,
490
+ title: `Configure ${entityName}`,
491
+ kind: "form",
492
+ resource: resourceKey,
493
+ capability: configureCapabilityKey
494
+ },
495
+ {
496
+ key: detailViewKey,
497
+ title: `${entityName} Detail`,
498
+ kind: "detail",
499
+ resource: resourceKey,
500
+ capability: syncCapabilityKey
501
+ }
502
+ ]
503
+ };
504
+ }
505
+ });
506
+ const billingPack = createLinkedEntityPack({
507
+ key: "billing",
508
+ title: "Billing Pack",
509
+ description: "Adds subscription and invoice primitives, durable collection tasks, and billing receipts.",
510
+ dependsOn: ["tenant"],
511
+ optionKeys: {
512
+ primaryName: "subscriptionName",
513
+ primaryPlural: "subscriptionPlural",
514
+ primaryResourceKey: "subscriptionResourceKey",
515
+ secondaryName: "invoiceName",
516
+ secondaryPlural: "invoicePlural",
517
+ secondaryResourceKey: "invoiceResourceKey",
518
+ primaryListCapabilityKey: "listSubscriptionsCapabilityKey",
519
+ primaryWriteCapabilityKey: "provisionSubscriptionCapabilityKey",
520
+ secondaryListCapabilityKey: "listInvoicesCapabilityKey",
521
+ secondaryExecuteCapabilityKey: "collectInvoicePaymentCapabilityKey",
522
+ taskKey: "taskKey",
523
+ artifactKey: "artifactKey",
524
+ approvalPolicyKey: "approvalPolicyKey",
525
+ primaryListViewKey: "subscriptionListViewKey",
526
+ primaryFormViewKey: "subscriptionFormViewKey",
527
+ secondaryListViewKey: "invoiceListViewKey",
528
+ secondaryDetailViewKey: "invoiceDetailViewKey"
529
+ },
530
+ primary: {
531
+ name: "Subscription",
532
+ resourceKey: "subscription",
533
+ description: (names) => `A billable ${names.primaryName.toLowerCase()} that governs service access for one tenant.`,
534
+ fields: {
535
+ planName: {
536
+ type: "string",
537
+ required: true
538
+ },
539
+ status: {
540
+ type: "string",
541
+ required: true,
542
+ constraints: {
543
+ enum: ["trial", "active", "past_due", "canceled"]
544
+ }
545
+ },
546
+ billingEmail: {
547
+ type: "string",
548
+ required: true
549
+ },
550
+ renewsAt: {
551
+ type: "datetime"
552
+ }
553
+ }
554
+ },
555
+ secondary: {
556
+ name: "Invoice",
557
+ resourceKey: "invoice",
558
+ description: (names) => `A billable ${names.secondaryName.toLowerCase()} tied to one ${names.primaryName.toLowerCase()}.`,
559
+ fields: (names) => ({
560
+ [`${names.primaryResourceKey}Id`]: {
561
+ type: "string",
562
+ required: true
563
+ },
564
+ amountCents: {
565
+ type: "integer",
566
+ required: true,
567
+ constraints: {
568
+ minimum: 0
569
+ }
570
+ },
571
+ currency: {
572
+ type: "string",
573
+ required: true
574
+ },
575
+ status: {
576
+ type: "string",
577
+ required: true,
578
+ constraints: {
579
+ enum: ["draft", "open", "paid", "failed", "void"]
580
+ }
581
+ },
582
+ dueAt: {
583
+ type: "date"
584
+ },
585
+ collectedAt: {
586
+ type: "datetime"
587
+ }
588
+ })
589
+ },
590
+ primaryList: {
591
+ description: (names) => `Read the ${names.primaryPlural.toLowerCase()} currently tracked by the billing system.`
592
+ },
593
+ primaryWrite: {
594
+ capabilityKey: (names) => `provision${names.primaryKeyStem}`,
595
+ title: (names) => `Provision ${names.primaryName}`,
596
+ description: (names) => `Create a new ${names.primaryName.toLowerCase()} and attach it to one tenant.`,
597
+ input: {
598
+ planName: {
599
+ type: "string",
600
+ required: true
601
+ },
602
+ billingEmail: {
603
+ type: "string",
604
+ required: true
605
+ }
606
+ },
607
+ viewTitle: (names) => `Provision ${names.primaryName}`
608
+ },
609
+ secondaryList: {
610
+ description: (names) => `Read the ${names.secondaryPlural.toLowerCase()} generated by the billing system.`
611
+ },
612
+ secondaryExecute: {
613
+ capabilityKey: (names) => `collect${names.secondaryKeyStem}Payment`,
614
+ title: (names) => `Collect ${names.secondaryName} Payment`,
615
+ description: (names) => `Run a durable payment collection flow for one ${names.secondaryName.toLowerCase()}.`,
616
+ taskTitle: (names) => `Collect ${names.secondaryName} Payment Task`,
617
+ taskDescription: (names) => `Durably collects payment for one ${names.secondaryName.toLowerCase()} and records the receipt.`,
618
+ artifactKey: (names) => `${names.secondaryResourceKey}CollectionReceipt`,
619
+ artifactTitle: (names) => `${names.secondaryName} Collection Receipt`,
620
+ artifactDescription: (names) => `A structured receipt artifact produced after one ${names.secondaryName.toLowerCase()} payment collection completes.`,
621
+ artifactKind: "record",
622
+ approvalPolicyKey: "billingCollectionApprovalRequired",
623
+ approvalTitle: (names) => `${names.secondaryName} Collection Approval Required`,
624
+ approvalDescription: (names) => `Requires approval before ${names.secondaryName.toLowerCase()} collection may continue.`,
625
+ viewTitle: (names) => `${names.secondaryName} Detail`
626
+ }
627
+ });
628
+ const commercePack = createLinkedEntityPack({
629
+ key: "commerce",
630
+ title: "Commerce Pack",
631
+ description: "Adds catalog and order primitives, durable fulfillment tasks, and fulfillment receipts.",
632
+ dependsOn: ["tenant"],
633
+ optionKeys: {
634
+ primaryName: "catalogItemName",
635
+ primaryPlural: "catalogItemPlural",
636
+ primaryResourceKey: "catalogItemResourceKey",
637
+ secondaryName: "orderName",
638
+ secondaryPlural: "orderPlural",
639
+ secondaryResourceKey: "orderResourceKey",
640
+ primaryListCapabilityKey: "listCatalogCapabilityKey",
641
+ primaryWriteCapabilityKey: "upsertCatalogCapabilityKey",
642
+ secondaryListCapabilityKey: "listOrdersCapabilityKey",
643
+ secondaryExecuteCapabilityKey: "fulfillOrderCapabilityKey",
644
+ taskKey: "taskKey",
645
+ artifactKey: "artifactKey",
646
+ approvalPolicyKey: "approvalPolicyKey",
647
+ primaryListViewKey: "catalogListViewKey",
648
+ primaryFormViewKey: "catalogFormViewKey",
649
+ secondaryListViewKey: "orderListViewKey",
650
+ secondaryDetailViewKey: "orderDetailViewKey"
651
+ },
652
+ primary: {
653
+ name: "Catalog Item",
654
+ resourceKey: "catalogItem",
655
+ description: (names) => `A sellable ${names.primaryName.toLowerCase()} in the application catalog.`,
656
+ fields: {
657
+ title: {
658
+ type: "string",
659
+ required: true
660
+ },
661
+ sku: {
662
+ type: "string",
663
+ required: true
664
+ },
665
+ priceCents: {
666
+ type: "integer",
667
+ required: true,
668
+ constraints: {
669
+ minimum: 0
670
+ }
671
+ },
672
+ status: {
673
+ type: "string",
674
+ required: true,
675
+ constraints: {
676
+ enum: ["draft", "active", "archived"]
677
+ }
678
+ }
679
+ }
680
+ },
681
+ secondary: {
682
+ name: "Order",
683
+ resourceKey: "order",
684
+ description: (names) => `A customer-facing ${names.secondaryName.toLowerCase()} that references one or more catalog items.`,
685
+ fields: (names) => ({
686
+ [`${names.primaryResourceKey}Id`]: {
687
+ type: "string",
688
+ required: true
689
+ },
690
+ quantity: {
691
+ type: "integer",
692
+ required: true,
693
+ constraints: {
694
+ minimum: 1
695
+ }
696
+ },
697
+ totalCents: {
698
+ type: "integer",
699
+ required: true,
700
+ constraints: {
701
+ minimum: 0
702
+ }
703
+ },
704
+ status: {
705
+ type: "string",
706
+ required: true,
707
+ constraints: {
708
+ enum: ["draft", "confirmed", "fulfilling", "fulfilled", "canceled"]
709
+ }
710
+ }
711
+ })
712
+ },
713
+ primaryList: {
714
+ description: (names) => `Read the ${names.primaryPlural.toLowerCase()} available to be ordered.`
715
+ },
716
+ primaryWrite: {
717
+ capabilityKey: (names) => `upsert${names.primaryKeyStem}`,
718
+ title: (names) => `Upsert ${names.primaryName}`,
719
+ description: (names) => `Create or update one ${names.primaryName.toLowerCase()} in the application catalog.`,
720
+ input: {
721
+ title: {
722
+ type: "string",
723
+ required: true
724
+ },
725
+ sku: {
726
+ type: "string",
727
+ required: true
728
+ },
729
+ priceCents: {
730
+ type: "integer",
731
+ required: true
732
+ }
733
+ },
734
+ viewTitle: (names) => `Upsert ${names.primaryName}`
735
+ },
736
+ secondaryList: {
737
+ description: (names) => `Read the ${names.secondaryPlural.toLowerCase()} currently managed by the commerce system.`
738
+ },
739
+ secondaryExecute: {
740
+ capabilityKey: (names) => `fulfill${names.secondaryKeyStem}`,
741
+ title: (names) => `Fulfill ${names.secondaryName}`,
742
+ description: (names) => `Run a durable fulfillment flow for one ${names.secondaryName.toLowerCase()}.`,
743
+ taskTitle: (names) => `Fulfill ${names.secondaryName} Task`,
744
+ taskDescription: (names) => `Durably fulfills one ${names.secondaryName.toLowerCase()} and records the fulfillment receipt.`,
745
+ artifactKey: (names) => `${names.secondaryResourceKey}FulfillmentReceipt`,
746
+ artifactTitle: (names) => `${names.secondaryName} Fulfillment Receipt`,
747
+ artifactDescription: (names) => `A structured receipt artifact produced after one ${names.secondaryName.toLowerCase()} fulfillment completes.`,
748
+ artifactKind: "record",
749
+ approvalPolicyKey: "commerceFulfillmentApprovalRequired",
750
+ approvalTitle: (names) => `${names.secondaryName} Fulfillment Approval Required`,
751
+ approvalDescription: (names) => `Requires approval before ${names.secondaryName.toLowerCase()} fulfillment may continue.`,
752
+ viewTitle: (names) => `${names.secondaryName} Detail`
753
+ }
754
+ });
755
+ const revenueOpsPack = createDurableEntityPack({
756
+ key: "revenueOps",
757
+ title: "Revenue Operations Pack",
758
+ description: "Bundles commerce, billing, and connectors, then adds a durable reconciliation surface on top.",
759
+ dependsOn: ["commerce", "billing", "connector"],
760
+ entity: {
761
+ name: "Revenue Ops Exception",
762
+ resourceKey: "revenueOpsException",
763
+ description: "A tracked issue raised when commerce, billing, or connector state drifts out of sync.",
764
+ fields: {
765
+ title: {
766
+ type: "string",
767
+ required: true
768
+ },
769
+ severity: {
770
+ type: "string",
771
+ required: true,
772
+ constraints: {
773
+ enum: ["low", "medium", "high", "critical"]
774
+ }
775
+ },
776
+ status: {
777
+ type: "string",
778
+ required: true,
779
+ constraints: {
780
+ enum: ["open", "investigating", "resolved"]
781
+ }
782
+ },
783
+ ownerId: {
784
+ type: "string"
785
+ }
786
+ }
787
+ },
788
+ list: {
789
+ description: "Read the revenue operations issues currently tracked across commerce and billing flows."
790
+ },
791
+ write: {
792
+ capabilityKey: "captureRevenueOpsException",
793
+ title: (names) => `Capture ${names.entityName}`,
794
+ description: "Create or update a revenue operations issue when a cross-system mismatch is discovered.",
795
+ input: {
796
+ title: {
797
+ type: "string",
798
+ required: true
799
+ },
800
+ severity: {
801
+ type: "string",
802
+ required: true
803
+ }
804
+ },
805
+ viewTitle: (names) => `Capture ${names.entityName}`
806
+ },
807
+ execute: {
808
+ capabilityKey: "reconcileRevenueOps",
809
+ title: "Reconcile Revenue Ops",
810
+ description: "Run a durable reconciliation flow across commerce, billing, and connector state.",
811
+ input: {
812
+ tenantId: {
813
+ type: "string",
814
+ required: true
815
+ }
816
+ },
817
+ taskTitle: "Reconcile Revenue Ops Task",
818
+ taskDescription: "Durably reconciles commerce, billing, and connector state into one operator digest.",
819
+ artifactKey: "revenueOpsDigest",
820
+ artifactTitle: "Revenue Ops Digest",
821
+ artifactDescription: "A generated digest artifact produced after one revenue operations reconciliation completes.",
822
+ artifactKind: "report",
823
+ approvalPolicyKey: "revenueOpsApprovalRequired",
824
+ approvalTitle: "Revenue Ops Approval Required",
825
+ approvalDescription: "Requires approval before revenue operations reconciliation may continue.",
826
+ viewTitle: (names) => `${names.entityName} Detail`
827
+ }
828
+ });
829
+ const builtinGraphPacks = [authPack, tenantPack, workflowPack, connectorPack, billingPack, commercePack, revenueOpsPack];
830
+ export function defineGraphPack(definition) {
831
+ return definition;
832
+ }
833
+ export function createDurableEntityPack(definition) {
834
+ return defineGraphPack({
835
+ key: definition.key,
836
+ title: definition.title,
837
+ ...(definition.description ? { description: definition.description } : {}),
838
+ ...(definition.dependsOn ? { dependsOn: definition.dependsOn } : {}),
839
+ apply(context) {
840
+ const names = resolveDurableEntityPackNames(context, definition);
841
+ const resourceFields = resolveRequiredTemplateRecord(definition.entity.fields, names);
842
+ const resourceTitle = renderTemplateValue(definition.entity.title, names) ?? names.entityName;
843
+ const resourceDescription = renderTemplateValue(definition.entity.description, names) ??
844
+ `A durable ${names.entityName.toLowerCase()} managed by this pack.`;
845
+ const listDescription = renderTemplateValue(definition.list?.description, names);
846
+ const listResources = resolveTemplateRecord(definition.list?.resources, names) ?? [
847
+ names.resourceKey
848
+ ];
849
+ const writeDescription = renderTemplateValue(definition.write.description, names);
850
+ const writeResources = resolveTemplateRecord(definition.write.resources, names) ?? [
851
+ names.resourceKey
852
+ ];
853
+ const writeInput = resolveRequiredTemplateRecord(definition.write.input, names);
854
+ const executeDescription = renderTemplateValue(definition.execute.description, names);
855
+ const executeResources = resolveTemplateRecord(definition.execute.resources, names) ?? [
856
+ names.resourceKey
857
+ ];
858
+ const executeInput = resolveTemplateRecord(definition.execute.input, names) ??
859
+ defaultExecuteInput(names);
860
+ const durableRuntime = createDurableRuntimeFragments({
861
+ taskKey: names.taskKey,
862
+ taskTitle: renderTemplateValue(definition.execute.taskTitle, names) ??
863
+ `${names.executeCapabilityKey} Task`,
864
+ taskDescription: renderTemplateValue(definition.execute.taskDescription, names) ??
865
+ `Durably executes ${names.executeCapabilityKey} and records one artifact.`,
866
+ artifactKey: names.artifactKey,
867
+ artifactTitle: renderTemplateValue(definition.execute.artifactTitle, names) ?? names.artifactKey,
868
+ artifactDescription: renderTemplateValue(definition.execute.artifactDescription, names) ??
869
+ `A generated artifact produced after ${names.executeCapabilityKey} completes.`,
870
+ artifactKind: definition.execute.artifactKind,
871
+ approvalPolicyKey: names.approvalPolicyKey,
872
+ approvalTitle: renderTemplateValue(definition.execute.approvalTitle, names) ??
873
+ `${names.entityName} Approval Required`,
874
+ approvalDescription: renderTemplateValue(definition.execute.approvalDescription, names) ??
875
+ `Requires approval before ${names.executeCapabilityKey} may continue.`
876
+ });
877
+ return {
878
+ resources: [
879
+ {
880
+ key: names.resourceKey,
881
+ title: resourceTitle,
882
+ description: resourceDescription,
883
+ fields: resourceFields
884
+ }
885
+ ],
886
+ capabilities: [
887
+ {
888
+ key: names.listCapabilityKey,
889
+ title: renderTemplateValue(definition.list?.title, names) ??
890
+ `List ${names.entityPlural}`,
891
+ ...(listDescription ? { description: listDescription } : {}),
892
+ mode: "read",
893
+ resources: listResources,
894
+ policy: names.accessPolicyKey
895
+ },
896
+ {
897
+ key: names.writeCapabilityKey,
898
+ title: renderTemplateValue(definition.write.title, names) ??
899
+ `Upsert ${names.entityName}`,
900
+ ...(writeDescription ? { description: writeDescription } : {}),
901
+ mode: "write",
902
+ resources: writeResources,
903
+ policy: names.accessPolicyKey,
904
+ input: writeInput
905
+ },
906
+ {
907
+ key: names.executeCapabilityKey,
908
+ title: renderTemplateValue(definition.execute.title, names) ??
909
+ `Execute ${names.entityName}`,
910
+ ...(executeDescription ? { description: executeDescription } : {}),
911
+ mode: "external",
912
+ resources: executeResources,
913
+ policy: names.approvalPolicyKey,
914
+ task: names.taskKey,
915
+ input: executeInput
916
+ }
917
+ ],
918
+ tasks: durableRuntime.tasks,
919
+ policies: durableRuntime.policies,
920
+ artifacts: durableRuntime.artifacts,
921
+ views: [
922
+ {
923
+ key: names.listViewKey,
924
+ title: renderTemplateValue(definition.list?.viewTitle, names) ?? names.entityPlural,
925
+ kind: "list",
926
+ resource: names.resourceKey,
927
+ capability: names.listCapabilityKey
928
+ },
929
+ {
930
+ key: names.formViewKey,
931
+ title: renderTemplateValue(definition.write.viewTitle, names) ??
932
+ `Upsert ${names.entityName}`,
933
+ kind: "form",
934
+ resource: names.resourceKey,
935
+ capability: names.writeCapabilityKey
936
+ },
937
+ {
938
+ key: names.detailViewKey,
939
+ title: renderTemplateValue(definition.execute.viewTitle, names) ??
940
+ `${names.entityName} Detail`,
941
+ kind: "detail",
942
+ resource: names.resourceKey,
943
+ capability: names.executeCapabilityKey
944
+ }
945
+ ]
946
+ };
947
+ }
948
+ });
949
+ }
950
+ export function createLinkedEntityPack(definition) {
951
+ return defineGraphPack({
952
+ key: definition.key,
953
+ title: definition.title,
954
+ ...(definition.description ? { description: definition.description } : {}),
955
+ ...(definition.dependsOn ? { dependsOn: definition.dependsOn } : {}),
956
+ apply(context) {
957
+ const names = resolveLinkedEntityPackNames(context, definition);
958
+ const primaryTitle = renderTemplateValue(definition.primary.title, names) ?? names.primaryName;
959
+ const primaryDescription = renderTemplateValue(definition.primary.description, names) ??
960
+ `A durable ${names.primaryName.toLowerCase()} managed by this pack.`;
961
+ const secondaryTitle = renderTemplateValue(definition.secondary.title, names) ?? names.secondaryName;
962
+ const secondaryDescription = renderTemplateValue(definition.secondary.description, names) ??
963
+ `A durable ${names.secondaryName.toLowerCase()} managed by this pack.`;
964
+ const primaryFields = resolveRequiredTemplateRecord(definition.primary.fields, names);
965
+ const secondaryFields = resolveRequiredTemplateRecord(definition.secondary.fields, names);
966
+ const primaryListDescription = renderTemplateValue(definition.primaryList?.description, names);
967
+ const primaryWriteDescription = renderTemplateValue(definition.primaryWrite.description, names);
968
+ const secondaryListDescription = renderTemplateValue(definition.secondaryList?.description, names);
969
+ const secondaryExecuteDescription = renderTemplateValue(definition.secondaryExecute.description, names);
970
+ const primaryListResources = resolveTemplateRecord(definition.primaryList?.resources, names) ?? [names.primaryResourceKey];
971
+ const primaryWriteResources = resolveTemplateRecord(definition.primaryWrite.resources, names) ?? [names.primaryResourceKey];
972
+ const secondaryListResources = resolveTemplateRecord(definition.secondaryList?.resources, names) ?? [names.secondaryResourceKey, names.primaryResourceKey];
973
+ const secondaryExecuteResources = resolveTemplateRecord(definition.secondaryExecute.resources, names) ?? [names.secondaryResourceKey, names.primaryResourceKey];
974
+ const primaryWriteInput = resolveRequiredTemplateRecord(definition.primaryWrite.input, names);
975
+ const secondaryExecuteInput = resolveTemplateRecord(definition.secondaryExecute.input, names) ??
976
+ defaultLinkedExecuteInput(names);
977
+ const durableRuntime = createDurableRuntimeFragments({
978
+ taskKey: names.taskKey,
979
+ taskTitle: renderTemplateValue(definition.secondaryExecute.taskTitle, names) ??
980
+ `${names.secondaryExecuteCapabilityKey} Task`,
981
+ taskDescription: renderTemplateValue(definition.secondaryExecute.taskDescription, names) ??
982
+ `Durably executes ${names.secondaryExecuteCapabilityKey} and records one artifact.`,
983
+ artifactKey: names.artifactKey,
984
+ artifactTitle: renderTemplateValue(definition.secondaryExecute.artifactTitle, names) ??
985
+ names.artifactKey,
986
+ artifactDescription: renderTemplateValue(definition.secondaryExecute.artifactDescription, names) ??
987
+ `A generated artifact produced after ${names.secondaryExecuteCapabilityKey} completes.`,
988
+ artifactKind: definition.secondaryExecute.artifactKind,
989
+ approvalPolicyKey: names.approvalPolicyKey,
990
+ approvalTitle: renderTemplateValue(definition.secondaryExecute.approvalTitle, names) ??
991
+ `${names.secondaryName} Approval Required`,
992
+ approvalDescription: renderTemplateValue(definition.secondaryExecute.approvalDescription, names) ??
993
+ `Requires approval before ${names.secondaryExecuteCapabilityKey} may continue.`
994
+ });
995
+ return {
996
+ resources: [
997
+ {
998
+ key: names.primaryResourceKey,
999
+ title: primaryTitle,
1000
+ description: primaryDescription,
1001
+ fields: primaryFields
1002
+ },
1003
+ {
1004
+ key: names.secondaryResourceKey,
1005
+ title: secondaryTitle,
1006
+ description: secondaryDescription,
1007
+ fields: secondaryFields
1008
+ }
1009
+ ],
1010
+ capabilities: [
1011
+ {
1012
+ key: names.primaryListCapabilityKey,
1013
+ title: renderTemplateValue(definition.primaryList?.title, names) ??
1014
+ `List ${names.primaryPlural}`,
1015
+ ...(primaryListDescription ? { description: primaryListDescription } : {}),
1016
+ mode: "read",
1017
+ resources: primaryListResources,
1018
+ policy: names.accessPolicyKey
1019
+ },
1020
+ {
1021
+ key: names.primaryWriteCapabilityKey,
1022
+ title: renderTemplateValue(definition.primaryWrite.title, names) ??
1023
+ `Upsert ${names.primaryName}`,
1024
+ ...(primaryWriteDescription ? { description: primaryWriteDescription } : {}),
1025
+ mode: "write",
1026
+ resources: primaryWriteResources,
1027
+ policy: names.accessPolicyKey,
1028
+ input: primaryWriteInput
1029
+ },
1030
+ {
1031
+ key: names.secondaryListCapabilityKey,
1032
+ title: renderTemplateValue(definition.secondaryList?.title, names) ??
1033
+ `List ${names.secondaryPlural}`,
1034
+ ...(secondaryListDescription ? { description: secondaryListDescription } : {}),
1035
+ mode: "read",
1036
+ resources: secondaryListResources,
1037
+ policy: names.accessPolicyKey
1038
+ },
1039
+ {
1040
+ key: names.secondaryExecuteCapabilityKey,
1041
+ title: renderTemplateValue(definition.secondaryExecute.title, names) ??
1042
+ `Execute ${names.secondaryName}`,
1043
+ ...(secondaryExecuteDescription ? { description: secondaryExecuteDescription } : {}),
1044
+ mode: "external",
1045
+ resources: secondaryExecuteResources,
1046
+ policy: names.approvalPolicyKey,
1047
+ task: names.taskKey,
1048
+ input: secondaryExecuteInput
1049
+ }
1050
+ ],
1051
+ tasks: durableRuntime.tasks,
1052
+ policies: durableRuntime.policies,
1053
+ artifacts: durableRuntime.artifacts,
1054
+ views: [
1055
+ {
1056
+ key: names.primaryListViewKey,
1057
+ title: renderTemplateValue(definition.primaryList?.viewTitle, names) ??
1058
+ names.primaryPlural,
1059
+ kind: "list",
1060
+ resource: names.primaryResourceKey,
1061
+ capability: names.primaryListCapabilityKey
1062
+ },
1063
+ {
1064
+ key: names.primaryFormViewKey,
1065
+ title: renderTemplateValue(definition.primaryWrite.viewTitle, names) ??
1066
+ `Upsert ${names.primaryName}`,
1067
+ kind: "form",
1068
+ resource: names.primaryResourceKey,
1069
+ capability: names.primaryWriteCapabilityKey
1070
+ },
1071
+ {
1072
+ key: names.secondaryListViewKey,
1073
+ title: renderTemplateValue(definition.secondaryList?.viewTitle, names) ??
1074
+ names.secondaryPlural,
1075
+ kind: "list",
1076
+ resource: names.secondaryResourceKey,
1077
+ capability: names.secondaryListCapabilityKey
1078
+ },
1079
+ {
1080
+ key: names.secondaryDetailViewKey,
1081
+ title: renderTemplateValue(definition.secondaryExecute.viewTitle, names) ??
1082
+ `${names.secondaryName} Detail`,
1083
+ kind: "detail",
1084
+ resource: names.secondaryResourceKey,
1085
+ capability: names.secondaryExecuteCapabilityKey
1086
+ }
1087
+ ]
1088
+ };
1089
+ }
1090
+ });
1091
+ }
1092
+ export function listBuiltinGraphPacks() {
1093
+ return [...builtinGraphPacks];
1094
+ }
1095
+ export function applyBuiltinAppGraphPacks(graph) {
1096
+ return applyAppGraphPacks(graph, builtinGraphPacks);
1097
+ }
1098
+ export function applyAppGraphPacks(graph, packs) {
1099
+ if (graph[PACKS_APPLIED]) {
1100
+ return graph;
1101
+ }
1102
+ const registry = new Map(packs.map((pack) => [pack.key, pack]));
1103
+ const resolvedSelections = resolvePackSelections(graph.packs ?? [], registry);
1104
+ const composed = cloneGraph(graph);
1105
+ composed.packs = resolvedSelections;
1106
+ const appliedPackKeys = [];
1107
+ for (const selection of resolvedSelections) {
1108
+ const pack = registry.get(selection.key);
1109
+ if (!pack) {
1110
+ throw new Error(`Unknown pack "${selection.key}".`);
1111
+ }
1112
+ const contribution = pack.apply({
1113
+ graph: cloneGraph(composed),
1114
+ selection,
1115
+ appliedPackKeys: [...appliedPackKeys]
1116
+ });
1117
+ composed.resources = mergeCollection(composed.resources, contribution.resources ?? [], "resource", selection.key);
1118
+ composed.capabilities = mergeCollection(composed.capabilities, contribution.capabilities ?? [], "capability", selection.key);
1119
+ composed.tasks = mergeCollection(composed.tasks ?? [], contribution.tasks ?? [], "task", selection.key);
1120
+ composed.policies = mergeCollection(composed.policies ?? [], contribution.policies ?? [], "policy", selection.key);
1121
+ composed.artifacts = mergeCollection(composed.artifacts ?? [], contribution.artifacts ?? [], "artifact", selection.key);
1122
+ composed.views = mergeCollection(composed.views ?? [], contribution.views ?? [], "view", selection.key);
1123
+ appliedPackKeys.push(selection.key);
1124
+ }
1125
+ return markPacksApplied(composed);
1126
+ }
1127
+ export function resolvePackSelections(selections, registry) {
1128
+ const explicitSelections = new Map();
1129
+ for (const selection of selections) {
1130
+ const key = selection.key.trim();
1131
+ if (!key) {
1132
+ throw new Error("Pack selections must not be empty.");
1133
+ }
1134
+ if (explicitSelections.has(key)) {
1135
+ throw new Error(`Duplicate pack selection "${key}".`);
1136
+ }
1137
+ explicitSelections.set(key, {
1138
+ key,
1139
+ ...(selection.options ? { options: cloneOptions(selection.options) } : {})
1140
+ });
1141
+ }
1142
+ const orderedSelections = [];
1143
+ const visiting = new Set();
1144
+ const visited = new Set();
1145
+ const visit = (key) => {
1146
+ if (visited.has(key)) {
1147
+ return;
1148
+ }
1149
+ if (visiting.has(key)) {
1150
+ throw new Error(`Pack dependency cycle detected at "${key}".`);
1151
+ }
1152
+ const pack = registry.get(key);
1153
+ if (!pack) {
1154
+ throw new Error(`Unknown pack "${key}".`);
1155
+ }
1156
+ visiting.add(key);
1157
+ for (const dependency of pack.dependsOn ?? []) {
1158
+ visit(dependency);
1159
+ }
1160
+ visiting.delete(key);
1161
+ visited.add(key);
1162
+ const explicitSelection = explicitSelections.get(key);
1163
+ orderedSelections.push(explicitSelection
1164
+ ? explicitSelection
1165
+ : {
1166
+ key
1167
+ });
1168
+ };
1169
+ for (const selection of explicitSelections.values()) {
1170
+ visit(selection.key);
1171
+ }
1172
+ return orderedSelections;
1173
+ }
1174
+ function mergeCollection(current, additions, label, packKey) {
1175
+ const keys = new Set(current.map((entry) => entry.key.trim()));
1176
+ const merged = [...current];
1177
+ for (const addition of additions) {
1178
+ const key = addition.key.trim();
1179
+ if (!key) {
1180
+ throw new Error(`Pack "${packKey}" tried to contribute an empty ${label} key.`);
1181
+ }
1182
+ if (keys.has(key)) {
1183
+ throw new Error(`Pack "${packKey}" cannot contribute ${label} "${key}" because the key already exists.`);
1184
+ }
1185
+ keys.add(key);
1186
+ merged.push(addition);
1187
+ }
1188
+ return merged;
1189
+ }
1190
+ function getAccessPolicyKey(context) {
1191
+ return context.appliedPackKeys.includes("tenant") ? "tenantScoped" : "authenticated";
1192
+ }
1193
+ function resolveDurableEntityPackNames(context, definition) {
1194
+ const entityName = readOptionString(context.selection, "entityName") ?? definition.entity.name;
1195
+ const entityPlural = readOptionString(context.selection, "entityPlural") ??
1196
+ definition.entity.plural ??
1197
+ pluralize(entityName);
1198
+ const entityKeyStem = toPackPascalName(entityName);
1199
+ const entityPluralKeyStem = toPackPascalName(entityPlural);
1200
+ const resourceKey = readOptionString(context.selection, "resourceKey") ??
1201
+ definition.entity.resourceKey ??
1202
+ toPackKey(entityName);
1203
+ const executeCapabilityKey = readOptionString(context.selection, "executeCapabilityKey") ??
1204
+ definition.execute.capabilityKey ??
1205
+ `execute${entityKeyStem}`;
1206
+ return {
1207
+ entityName,
1208
+ entityPlural,
1209
+ resourceKey,
1210
+ entityKeyStem,
1211
+ entityPluralKeyStem,
1212
+ accessPolicyKey: getAccessPolicyKey(context),
1213
+ listCapabilityKey: readOptionString(context.selection, "listCapabilityKey") ??
1214
+ definition.list?.capabilityKey ??
1215
+ `list${entityPluralKeyStem}`,
1216
+ writeCapabilityKey: readOptionString(context.selection, "writeCapabilityKey") ??
1217
+ definition.write.capabilityKey ??
1218
+ `upsert${entityKeyStem}`,
1219
+ executeCapabilityKey,
1220
+ taskKey: readOptionString(context.selection, "taskKey") ??
1221
+ definition.execute.taskKey ??
1222
+ `${executeCapabilityKey}Task`,
1223
+ artifactKey: readOptionString(context.selection, "artifactKey") ??
1224
+ definition.execute.artifactKey ??
1225
+ `${resourceKey}Artifact`,
1226
+ approvalPolicyKey: readOptionString(context.selection, "approvalPolicyKey") ??
1227
+ definition.execute.approvalPolicyKey ??
1228
+ `${resourceKey}ApprovalRequired`,
1229
+ listViewKey: readOptionString(context.selection, "listViewKey") ??
1230
+ definition.list?.viewKey ??
1231
+ `${resourceKey}List`,
1232
+ formViewKey: readOptionString(context.selection, "formViewKey") ??
1233
+ definition.write.viewKey ??
1234
+ `${resourceKey}Form`,
1235
+ detailViewKey: readOptionString(context.selection, "detailViewKey") ??
1236
+ definition.execute.viewKey ??
1237
+ `${resourceKey}Detail`
1238
+ };
1239
+ }
1240
+ function resolveLinkedEntityPackNames(context, definition) {
1241
+ const optionKeys = definition.optionKeys ?? {};
1242
+ const primaryName = readOptionString(context.selection, optionKeys.primaryName ?? "primaryName") ??
1243
+ definition.primary.name;
1244
+ const primaryPlural = readOptionString(context.selection, optionKeys.primaryPlural ?? "primaryPlural") ??
1245
+ definition.primary.plural ??
1246
+ pluralize(primaryName);
1247
+ const primaryKeyStem = toPackPascalName(primaryName);
1248
+ const primaryPluralKeyStem = toPackPascalName(primaryPlural);
1249
+ const primaryResourceKey = readOptionString(context.selection, optionKeys.primaryResourceKey ?? "primaryResourceKey") ??
1250
+ definition.primary.resourceKey ??
1251
+ toPackKey(primaryName);
1252
+ const secondaryName = readOptionString(context.selection, optionKeys.secondaryName ?? "secondaryName") ??
1253
+ definition.secondary.name;
1254
+ const secondaryPlural = readOptionString(context.selection, optionKeys.secondaryPlural ?? "secondaryPlural") ??
1255
+ definition.secondary.plural ??
1256
+ pluralize(secondaryName);
1257
+ const secondaryKeyStem = toPackPascalName(secondaryName);
1258
+ const secondaryPluralKeyStem = toPackPascalName(secondaryPlural);
1259
+ const secondaryResourceKey = readOptionString(context.selection, optionKeys.secondaryResourceKey ?? "secondaryResourceKey") ??
1260
+ definition.secondary.resourceKey ??
1261
+ toPackKey(secondaryName);
1262
+ const accessPolicyKey = getAccessPolicyKey(context);
1263
+ const primaryListCapabilityKey = readOptionString(context.selection, optionKeys.primaryListCapabilityKey ?? "primaryListCapabilityKey") ??
1264
+ renderTemplateValue(definition.primaryList?.capabilityKey, {
1265
+ primaryName,
1266
+ primaryPlural,
1267
+ primaryResourceKey,
1268
+ primaryKeyStem,
1269
+ primaryPluralKeyStem,
1270
+ secondaryName,
1271
+ secondaryPlural,
1272
+ secondaryResourceKey,
1273
+ secondaryKeyStem,
1274
+ secondaryPluralKeyStem,
1275
+ accessPolicyKey
1276
+ }) ??
1277
+ `list${primaryPluralKeyStem}`;
1278
+ const primaryWriteCapabilityKey = readOptionString(context.selection, optionKeys.primaryWriteCapabilityKey ?? "primaryWriteCapabilityKey") ??
1279
+ renderTemplateValue(definition.primaryWrite.capabilityKey, {
1280
+ primaryName,
1281
+ primaryPlural,
1282
+ primaryResourceKey,
1283
+ primaryKeyStem,
1284
+ primaryPluralKeyStem,
1285
+ secondaryName,
1286
+ secondaryPlural,
1287
+ secondaryResourceKey,
1288
+ secondaryKeyStem,
1289
+ secondaryPluralKeyStem,
1290
+ accessPolicyKey
1291
+ }) ??
1292
+ `upsert${primaryKeyStem}`;
1293
+ const secondaryListCapabilityKey = readOptionString(context.selection, optionKeys.secondaryListCapabilityKey ?? "secondaryListCapabilityKey") ??
1294
+ renderTemplateValue(definition.secondaryList?.capabilityKey, {
1295
+ primaryName,
1296
+ primaryPlural,
1297
+ primaryResourceKey,
1298
+ primaryKeyStem,
1299
+ primaryPluralKeyStem,
1300
+ secondaryName,
1301
+ secondaryPlural,
1302
+ secondaryResourceKey,
1303
+ secondaryKeyStem,
1304
+ secondaryPluralKeyStem,
1305
+ accessPolicyKey
1306
+ }) ??
1307
+ `list${secondaryPluralKeyStem}`;
1308
+ const secondaryExecuteCapabilityKey = readOptionString(context.selection, optionKeys.secondaryExecuteCapabilityKey ?? "secondaryExecuteCapabilityKey") ??
1309
+ renderTemplateValue(definition.secondaryExecute.capabilityKey, {
1310
+ primaryName,
1311
+ primaryPlural,
1312
+ primaryResourceKey,
1313
+ primaryKeyStem,
1314
+ primaryPluralKeyStem,
1315
+ secondaryName,
1316
+ secondaryPlural,
1317
+ secondaryResourceKey,
1318
+ secondaryKeyStem,
1319
+ secondaryPluralKeyStem,
1320
+ accessPolicyKey,
1321
+ primaryListCapabilityKey,
1322
+ primaryWriteCapabilityKey,
1323
+ secondaryListCapabilityKey
1324
+ }) ??
1325
+ `execute${secondaryKeyStem}`;
1326
+ const taskKey = readOptionString(context.selection, optionKeys.taskKey ?? "taskKey") ??
1327
+ renderTemplateValue(definition.secondaryExecute.taskKey, {
1328
+ primaryName,
1329
+ primaryPlural,
1330
+ primaryResourceKey,
1331
+ primaryKeyStem,
1332
+ primaryPluralKeyStem,
1333
+ secondaryName,
1334
+ secondaryPlural,
1335
+ secondaryResourceKey,
1336
+ secondaryKeyStem,
1337
+ secondaryPluralKeyStem,
1338
+ accessPolicyKey,
1339
+ primaryListCapabilityKey,
1340
+ primaryWriteCapabilityKey,
1341
+ secondaryListCapabilityKey,
1342
+ secondaryExecuteCapabilityKey
1343
+ }) ??
1344
+ `${secondaryExecuteCapabilityKey}Task`;
1345
+ const artifactKey = readOptionString(context.selection, optionKeys.artifactKey ?? "artifactKey") ??
1346
+ renderTemplateValue(definition.secondaryExecute.artifactKey, {
1347
+ primaryName,
1348
+ primaryPlural,
1349
+ primaryResourceKey,
1350
+ primaryKeyStem,
1351
+ primaryPluralKeyStem,
1352
+ secondaryName,
1353
+ secondaryPlural,
1354
+ secondaryResourceKey,
1355
+ secondaryKeyStem,
1356
+ secondaryPluralKeyStem,
1357
+ accessPolicyKey,
1358
+ primaryListCapabilityKey,
1359
+ primaryWriteCapabilityKey,
1360
+ secondaryListCapabilityKey,
1361
+ secondaryExecuteCapabilityKey,
1362
+ taskKey
1363
+ }) ??
1364
+ `${secondaryResourceKey}Artifact`;
1365
+ const approvalPolicyKey = readOptionString(context.selection, optionKeys.approvalPolicyKey ?? "approvalPolicyKey") ??
1366
+ renderTemplateValue(definition.secondaryExecute.approvalPolicyKey, {
1367
+ primaryName,
1368
+ primaryPlural,
1369
+ primaryResourceKey,
1370
+ primaryKeyStem,
1371
+ primaryPluralKeyStem,
1372
+ secondaryName,
1373
+ secondaryPlural,
1374
+ secondaryResourceKey,
1375
+ secondaryKeyStem,
1376
+ secondaryPluralKeyStem,
1377
+ accessPolicyKey,
1378
+ primaryListCapabilityKey,
1379
+ primaryWriteCapabilityKey,
1380
+ secondaryListCapabilityKey,
1381
+ secondaryExecuteCapabilityKey,
1382
+ taskKey,
1383
+ artifactKey
1384
+ }) ??
1385
+ `${secondaryResourceKey}ApprovalRequired`;
1386
+ const primaryListViewKey = readOptionString(context.selection, optionKeys.primaryListViewKey ?? "primaryListViewKey") ??
1387
+ renderTemplateValue(definition.primaryList?.viewKey, {
1388
+ primaryName,
1389
+ primaryPlural,
1390
+ primaryResourceKey
1391
+ }) ??
1392
+ `${primaryResourceKey}List`;
1393
+ const primaryFormViewKey = readOptionString(context.selection, optionKeys.primaryFormViewKey ?? "primaryFormViewKey") ??
1394
+ renderTemplateValue(definition.primaryWrite.viewKey, {
1395
+ primaryName,
1396
+ primaryPlural,
1397
+ primaryResourceKey
1398
+ }) ??
1399
+ `${primaryResourceKey}Form`;
1400
+ const secondaryListViewKey = readOptionString(context.selection, optionKeys.secondaryListViewKey ?? "secondaryListViewKey") ??
1401
+ renderTemplateValue(definition.secondaryList?.viewKey, {
1402
+ secondaryName,
1403
+ secondaryPlural,
1404
+ secondaryResourceKey
1405
+ }) ??
1406
+ `${secondaryResourceKey}List`;
1407
+ const secondaryDetailViewKey = readOptionString(context.selection, optionKeys.secondaryDetailViewKey ?? "secondaryDetailViewKey") ??
1408
+ renderTemplateValue(definition.secondaryExecute.viewKey, {
1409
+ secondaryName,
1410
+ secondaryPlural,
1411
+ secondaryResourceKey
1412
+ }) ??
1413
+ `${secondaryResourceKey}Detail`;
1414
+ return {
1415
+ primaryName,
1416
+ primaryPlural,
1417
+ primaryResourceKey,
1418
+ primaryKeyStem,
1419
+ primaryPluralKeyStem,
1420
+ secondaryName,
1421
+ secondaryPlural,
1422
+ secondaryResourceKey,
1423
+ secondaryKeyStem,
1424
+ secondaryPluralKeyStem,
1425
+ accessPolicyKey,
1426
+ primaryListCapabilityKey,
1427
+ primaryWriteCapabilityKey,
1428
+ secondaryListCapabilityKey,
1429
+ secondaryExecuteCapabilityKey,
1430
+ taskKey,
1431
+ artifactKey,
1432
+ approvalPolicyKey,
1433
+ primaryListViewKey,
1434
+ primaryFormViewKey,
1435
+ secondaryListViewKey,
1436
+ secondaryDetailViewKey
1437
+ };
1438
+ }
1439
+ function resolveTemplateRecord(value, names) {
1440
+ if (!value) {
1441
+ return undefined;
1442
+ }
1443
+ return typeof value === "function"
1444
+ ? value(names)
1445
+ : value;
1446
+ }
1447
+ function resolveRequiredTemplateRecord(value, names) {
1448
+ return typeof value === "function"
1449
+ ? value(names)
1450
+ : value;
1451
+ }
1452
+ function renderTemplateValue(value, names) {
1453
+ if (!value) {
1454
+ return undefined;
1455
+ }
1456
+ return typeof value === "function" ? value(names) : value;
1457
+ }
1458
+ function defaultExecuteInput(names) {
1459
+ return {
1460
+ [`${names.resourceKey}Id`]: {
1461
+ type: "string",
1462
+ required: true
1463
+ }
1464
+ };
1465
+ }
1466
+ function defaultLinkedExecuteInput(names) {
1467
+ return {
1468
+ [`${names.secondaryResourceKey}Id`]: {
1469
+ type: "string",
1470
+ required: true
1471
+ }
1472
+ };
1473
+ }
1474
+ function createDurableRuntimeFragments(config) {
1475
+ return {
1476
+ tasks: [
1477
+ {
1478
+ key: config.taskKey,
1479
+ title: config.taskTitle,
1480
+ description: config.taskDescription,
1481
+ kind: "durable",
1482
+ artifacts: [config.artifactKey]
1483
+ }
1484
+ ],
1485
+ policies: [
1486
+ {
1487
+ key: config.approvalPolicyKey,
1488
+ title: config.approvalTitle,
1489
+ description: config.approvalDescription,
1490
+ effect: "approve"
1491
+ }
1492
+ ],
1493
+ artifacts: [
1494
+ {
1495
+ key: config.artifactKey,
1496
+ title: config.artifactTitle,
1497
+ description: config.artifactDescription,
1498
+ kind: config.artifactKind
1499
+ }
1500
+ ]
1501
+ };
1502
+ }
1503
+ function cloneGraph(graph) {
1504
+ const cloned = {
1505
+ ...(typeof graph.version === "number" ? { version: graph.version } : {}),
1506
+ domain: cloneValue(graph.domain),
1507
+ ...(graph.packs ? { packs: cloneValue(graph.packs) } : {}),
1508
+ resources: cloneValue(graph.resources),
1509
+ capabilities: cloneValue(graph.capabilities),
1510
+ tasks: cloneValue(graph.tasks ?? []),
1511
+ policies: cloneValue(graph.policies ?? []),
1512
+ artifacts: cloneValue(graph.artifacts ?? []),
1513
+ views: cloneValue(graph.views ?? [])
1514
+ };
1515
+ if (graph[PACKS_APPLIED]) {
1516
+ cloned[PACKS_APPLIED] = true;
1517
+ }
1518
+ return cloned;
1519
+ }
1520
+ function cloneOptions(options) {
1521
+ return cloneValue(options);
1522
+ }
1523
+ function cloneValue(value) {
1524
+ return JSON.parse(JSON.stringify(value));
1525
+ }
1526
+ function markPacksApplied(graph) {
1527
+ graph[PACKS_APPLIED] = true;
1528
+ return graph;
1529
+ }
1530
+ function readOptionString(selection, key) {
1531
+ const value = selection.options?.[key];
1532
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1533
+ }
1534
+ function pluralize(value) {
1535
+ const trimmed = value.trim();
1536
+ if (trimmed.endsWith("s")) {
1537
+ return `${trimmed}es`;
1538
+ }
1539
+ if (trimmed.endsWith("y") && trimmed.length > 1) {
1540
+ return `${trimmed.slice(0, -1)}ies`;
1541
+ }
1542
+ return `${trimmed}s`;
1543
+ }
1544
+ function toPackKey(value) {
1545
+ const parts = value
1546
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
1547
+ .replace(/[^a-zA-Z0-9]+/g, " ")
1548
+ .trim()
1549
+ .split(/\s+/)
1550
+ .filter(Boolean);
1551
+ if (!parts.length) {
1552
+ return "packResource";
1553
+ }
1554
+ const [first, ...rest] = parts;
1555
+ return [
1556
+ first.toLowerCase(),
1557
+ ...rest.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
1558
+ ].join("");
1559
+ }
1560
+ function toPackPascalName(value) {
1561
+ const parts = value
1562
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
1563
+ .replace(/[^a-zA-Z0-9]+/g, " ")
1564
+ .trim()
1565
+ .split(/\s+/)
1566
+ .filter(Boolean);
1567
+ if (!parts.length) {
1568
+ return "PackEntity";
1569
+ }
1570
+ return parts
1571
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
1572
+ .join("");
1573
+ }
1574
+ //# sourceMappingURL=index.js.map