@synap-core/workspace-templates 0.9.2 → 0.9.4

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/src/index.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * const template = getWorkspaceTemplate('brand-library');
12
12
  * // template.workspace.name === 'Brand Library'
13
13
  * // template.app.kind === 'native'
14
- * // template.layout.defaultApp === 'brandLibrary'
14
+ * // template.app.appId === 'brandLibrary'
15
15
  *
16
16
  * YAML source files live in src/*.yaml — AI-friendly, commentable, mergeable.
17
17
  * Run `pnpm build` to regenerate src/templates.ts from YAML.
@@ -22,6 +22,10 @@ export type {
22
22
  TemplateMeta,
23
23
  TemplateAppRef,
24
24
  TemplateAppKind,
25
+ WorkspaceJsonValue,
26
+ WorkspacePrimarySurface,
27
+ WorkspacePrimarySurfaceViewReference,
28
+ TemplatePrimarySurface,
25
29
  TemplateProfile,
26
30
  TemplateProperty,
27
31
  TemplateView,
@@ -86,8 +90,7 @@ export type {
86
90
  } from "./catalog.js";
87
91
 
88
92
  export {
89
- layerTemplateGraph,
90
- bundledEdgesOf,
93
+ layerTemplateGraph, bundledEdgesOf
91
94
  } from "./composition-graph.js";
92
95
  export type {
93
96
  CompositionInstalledEntry,
@@ -138,10 +141,30 @@ export type {
138
141
  TemplateConverters,
139
142
  } from "./validate.js";
140
143
 
144
+ // The pure, DB-free automation-flow node-contract validator. `validateTemplate`
145
+ // runs it over every `automations[].flow` so broken flows (unknown node type,
146
+ // dangling edge, cycle, typo'd channelType, verb-less capability node) fail at
147
+ // author time instead of silently never firing on a live pod. Exported so the
148
+ // pod's persist doors + the (post-publish) backend re-export share this SSOT.
149
+
150
+ export {
151
+ validateFlowDefinition,
152
+ flowValidationErrorMessage,
153
+ FLOW_NODE_TYPES,
154
+ FLOW_OUTPUT_TYPES,
155
+ CHANNEL_MESSAGE_CHANNEL_TYPES,
156
+ } from "./validate-flow.js";
157
+ export type {
158
+ FlowValidationError,
159
+ FlowValidationResult,
160
+ FlowValidationResolvers,
161
+ } from "./validate-flow.js";
162
+
141
163
  export {
142
164
  toWorkspaceDefinition,
143
165
  toPackageDefinition,
144
166
  splitBentoBlocks,
167
+ normalizeTemplateLayout,
145
168
  } from "./define.js";
146
169
  export type {
147
170
  WorkspaceDefinitionInput,
@@ -92,6 +92,25 @@ profiles:
92
92
  label: Notes
93
93
  valueType: string
94
94
  inputType: textarea
95
+ # `campaign-type` is the campaign's NATURE/phase, orthogonal to
96
+ # `campaign-channel` (the delivery medium). scoping/outreach/live have no
97
+ # channel equivalent, so folding them into `campaign-channel` would lose
98
+ # them — they get their own def. Kept verbatim across the three campaign
99
+ # declarations (business-developer / content-os / here) so there is no drift.
100
+ - slug: campaign-type
101
+ label: Type
102
+ valueType: string
103
+ inputType: select
104
+ enumValues:
105
+ [scoping, outreach, live, partnership, event, other]
106
+ constraints: { defaultValue: outreach }
107
+ # `campaign-color` backs the per-campaign colour the CRM surfaces render.
108
+ # Stored as a palette NAME (see apps/crm/lib/campaignColors.ts), not a hex.
109
+ - slug: campaign-color
110
+ label: Color
111
+ valueType: string
112
+ inputType: select
113
+ enumValues: [indigo, violet, amber, cyan, pink, teal]
95
114
 
96
115
  - slug: contact
97
116
  displayName: Contact
package/src/templates.ts CHANGED
@@ -6833,14 +6833,13 @@ const DATA = {
6833
6833
  "valueType": "string",
6834
6834
  "inputType": "select",
6835
6835
  "enumValues": [
6836
- "manual",
6837
- "referral",
6836
+ "cold-outreach",
6838
6837
  "linkedin",
6839
- "email",
6838
+ "twitter",
6839
+ "landing-page",
6840
+ "inbound-call",
6841
+ "referral",
6840
6842
  "event",
6841
- "inbound",
6842
- "partner",
6843
- "import",
6844
6843
  "other"
6845
6844
  ]
6846
6845
  },
@@ -7210,6 +7209,37 @@ const DATA = {
7210
7209
  "label": "Notes",
7211
7210
  "valueType": "string",
7212
7211
  "inputType": "textarea"
7212
+ },
7213
+ {
7214
+ "slug": "campaign-type",
7215
+ "label": "Type",
7216
+ "valueType": "string",
7217
+ "inputType": "select",
7218
+ "enumValues": [
7219
+ "scoping",
7220
+ "outreach",
7221
+ "live",
7222
+ "partnership",
7223
+ "event",
7224
+ "other"
7225
+ ],
7226
+ "constraints": {
7227
+ "defaultValue": "outreach"
7228
+ }
7229
+ },
7230
+ {
7231
+ "slug": "campaign-color",
7232
+ "label": "Color",
7233
+ "valueType": "string",
7234
+ "inputType": "select",
7235
+ "enumValues": [
7236
+ "indigo",
7237
+ "violet",
7238
+ "amber",
7239
+ "cyan",
7240
+ "pink",
7241
+ "teal"
7242
+ ]
7213
7243
  }
7214
7244
  ]
7215
7245
  }
@@ -8909,6 +8939,37 @@ const DATA = {
8909
8939
  "label": "Notes",
8910
8940
  "valueType": "string",
8911
8941
  "inputType": "textarea"
8942
+ },
8943
+ {
8944
+ "slug": "campaign-type",
8945
+ "label": "Type",
8946
+ "valueType": "string",
8947
+ "inputType": "select",
8948
+ "enumValues": [
8949
+ "scoping",
8950
+ "outreach",
8951
+ "live",
8952
+ "partnership",
8953
+ "event",
8954
+ "other"
8955
+ ],
8956
+ "constraints": {
8957
+ "defaultValue": "outreach"
8958
+ }
8959
+ },
8960
+ {
8961
+ "slug": "campaign-color",
8962
+ "label": "Color",
8963
+ "valueType": "string",
8964
+ "inputType": "select",
8965
+ "enumValues": [
8966
+ "indigo",
8967
+ "violet",
8968
+ "amber",
8969
+ "cyan",
8970
+ "pink",
8971
+ "teal"
8972
+ ]
8912
8973
  }
8913
8974
  ]
8914
8975
  },
@@ -10009,6 +10070,12 @@ const DATA = {
10009
10070
  ],
10010
10071
  "isPublic": true
10011
10072
  },
10073
+ "app": {
10074
+ "kind": "url",
10075
+ "appId": "crm",
10076
+ "url": "https://crm.synap.live",
10077
+ "title": "CRM"
10078
+ },
10012
10079
  "dependencies": [
10013
10080
  {
10014
10081
  "slug": "foundation",
@@ -10079,7 +10146,17 @@ const DATA = {
10079
10146
  "slug": "source",
10080
10147
  "label": "Source",
10081
10148
  "valueType": "string",
10082
- "inputType": "text"
10149
+ "inputType": "select",
10150
+ "enumValues": [
10151
+ "cold-outreach",
10152
+ "linkedin",
10153
+ "twitter",
10154
+ "landing-page",
10155
+ "inbound-call",
10156
+ "referral",
10157
+ "event",
10158
+ "other"
10159
+ ]
10083
10160
  },
10084
10161
  {
10085
10162
  "slug": "icpScore",
@@ -10168,7 +10245,17 @@ const DATA = {
10168
10245
  "slug": "source",
10169
10246
  "label": "Source",
10170
10247
  "valueType": "string",
10171
- "inputType": "text"
10248
+ "inputType": "select",
10249
+ "enumValues": [
10250
+ "cold-outreach",
10251
+ "linkedin",
10252
+ "twitter",
10253
+ "landing-page",
10254
+ "inbound-call",
10255
+ "referral",
10256
+ "event",
10257
+ "other"
10258
+ ]
10172
10259
  },
10173
10260
  {
10174
10261
  "slug": "icpScore",
@@ -10556,6 +10643,66 @@ const DATA = {
10556
10643
  "type": "linked_to_deal"
10557
10644
  }
10558
10645
  ],
10646
+ "onboarding": {
10647
+ "goal": "Turn the user's real commercial motion into a usable pipeline — the right companies and people, their active opportunities, and an explicit next step for every deal — so CRM becomes the place sales work moves forward.\n",
10648
+ "framing": "THE REVENUE OPERATOR: Act as a pragmatic sales lead setting up a CRM people will actually maintain. Start from the user's existing motion, vocabulary, and active conversations. Resolve before creating so imported or Pod-wide people are reused rather than duplicated. Push for a concrete next step and date; never invent revenue, probability, or contact details.\n",
10649
+ "expertise": {
10650
+ "starters": [
10651
+ "A useful first pipeline is deliberately small. Start with the few stages that change how the team acts — contacted, qualifying, proposal, negotiating, won, and lost — and keep the template's default stages unless the user's real process needs a different view.\n",
10652
+ "Populate the workspace with live work, not a perfect historical archive: propose the three to five opportunities the user is actively trying to move, then add the companies and people needed to understand those deals.\n",
10653
+ "Every active deal needs one observable next step with a date. If the next step is \"follow up\" or \"keep warm\", help turn it into a specific action such as \"send the revised proposal Thursday\" before capturing it.\n"
10654
+ ],
10655
+ "blindSpots": [
10656
+ "A deal without a real person is usually an account-shaped hope. If an opportunity has no stakeholder, surface the gap instead of fabricating a placeholder contact.\n",
10657
+ "Pipelines become fiction when stages describe confidence rather than an event the buyer completed. Ask what evidence moves a deal into its current stage, especially around proposal and negotiation.\n",
10658
+ "More fields do not create better sales hygiene. Prioritize stage, value when known, expected close date, and next step; leave unverifiable fields empty and offer enrichment separately.\n"
10659
+ ],
10660
+ "bar": "A sales lead can open the pipeline and immediately see the few live opportunities, the real company and stakeholder behind each one, its evidence-based stage, and the dated next action — with no duplicate people and no invented commercial data.\n"
10661
+ },
10662
+ "collect": [
10663
+ {
10664
+ "profileSlug": "company",
10665
+ "what": "The real prospect organisations behind current conversations.",
10666
+ "cardinality": "few",
10667
+ "keyFields": [
10668
+ "website",
10669
+ "industry",
10670
+ "companySize",
10671
+ "source",
10672
+ "nextStep"
10673
+ ]
10674
+ },
10675
+ {
10676
+ "profileSlug": "person",
10677
+ "what": "The known stakeholders for those organisations and opportunities.",
10678
+ "cardinality": "few",
10679
+ "keyFields": [
10680
+ "email",
10681
+ "jobTitle",
10682
+ "company",
10683
+ "source",
10684
+ "nextFollowUpDate"
10685
+ ]
10686
+ },
10687
+ {
10688
+ "profileSlug": "deal",
10689
+ "what": "The live opportunities worth actively moving through the pipeline.",
10690
+ "cardinality": "few",
10691
+ "keyFields": [
10692
+ "dealStage",
10693
+ "value",
10694
+ "closeDate",
10695
+ "nextStep"
10696
+ ]
10697
+ }
10698
+ ],
10699
+ "openingQuestions": [
10700
+ "Which three to five opportunities are you actively trying to move right now?",
10701
+ "For the most important one, which company and real stakeholder are involved?",
10702
+ "What has the buyer actually done so far, and what is the next dated action?"
10703
+ ],
10704
+ "doneWhen": "At least one real opportunity is linked to its company or stakeholder and has an evidence-based stage plus a concrete next step; the user can see how to add or import the rest without duplicating existing people.\n"
10705
+ },
10559
10706
  "playbooks": [
10560
10707
  {
10561
10708
  "name": "Enrich Lead/Company",
@@ -22004,6 +22151,37 @@ const DATA = {
22004
22151
  "label": "Notes",
22005
22152
  "valueType": "string",
22006
22153
  "inputType": "textarea"
22154
+ },
22155
+ {
22156
+ "slug": "campaign-type",
22157
+ "label": "Type",
22158
+ "valueType": "string",
22159
+ "inputType": "select",
22160
+ "enumValues": [
22161
+ "scoping",
22162
+ "outreach",
22163
+ "live",
22164
+ "partnership",
22165
+ "event",
22166
+ "other"
22167
+ ],
22168
+ "constraints": {
22169
+ "defaultValue": "outreach"
22170
+ }
22171
+ },
22172
+ {
22173
+ "slug": "campaign-color",
22174
+ "label": "Color",
22175
+ "valueType": "string",
22176
+ "inputType": "select",
22177
+ "enumValues": [
22178
+ "indigo",
22179
+ "violet",
22180
+ "amber",
22181
+ "cyan",
22182
+ "pink",
22183
+ "teal"
22184
+ ]
22007
22185
  }
22008
22186
  ]
22009
22187
  },
package/src/types.ts CHANGED
@@ -10,24 +10,97 @@
10
10
  // App Reference — what opens as first screen when entering this workspace
11
11
  // ============================================================================
12
12
 
13
- export type TemplateAppKind = 'native' | 'stored' | 'url';
13
+ export type TemplateAppKind = "native" | "stored" | "url";
14
14
 
15
15
  export interface TemplateAppRef {
16
16
  kind: TemplateAppKind;
17
17
  /** For kind='native': the appId registered in the browser manifest */
18
18
  appId?: string;
19
19
  /** For kind='native': how the platform renders the app */
20
- rendererType?: 'native' | 'external' | 'iframe-srcdoc';
20
+ rendererType?: "native" | "external" | "iframe-srcdoc";
21
21
  /** For kind='stored': the cell key registered in the cell registry */
22
22
  cellKey?: string;
23
23
  /** For kind='stored': default props passed to the cell */
24
- props?: Record<string, unknown>;
24
+ props?: Record<string, WorkspaceJsonValue>;
25
25
  /** For kind='url': external URL to open */
26
26
  url?: string;
27
27
  /** Human label */
28
28
  title?: string;
29
29
  }
30
30
 
31
+ // ============================================================================
32
+ // Workspace primary surface — JSON-safe persisted subset of SurfaceDescriptor
33
+ // ============================================================================
34
+
35
+ export type WorkspaceJsonValue =
36
+ | string
37
+ | number
38
+ | boolean
39
+ | null
40
+ | WorkspaceJsonValue[]
41
+ | { [key: string]: WorkspaceJsonValue };
42
+
43
+ export type WorkspacePrimarySurface =
44
+ | {
45
+ kind: "app";
46
+ appId: string;
47
+ rendererType: "native";
48
+ title?: string;
49
+ props?: Record<string, WorkspaceJsonValue>;
50
+ }
51
+ | {
52
+ kind: "app";
53
+ appId: string;
54
+ rendererType: "external";
55
+ url: string;
56
+ title?: string;
57
+ props?: Record<string, WorkspaceJsonValue>;
58
+ }
59
+ | {
60
+ kind: "cell";
61
+ cellKey: string;
62
+ title?: string;
63
+ props?: Record<string, WorkspaceJsonValue>;
64
+ }
65
+ | {
66
+ /**
67
+ * Ordinary website. Unlike an external `app`, this has no trusted app
68
+ * identity and is always opened through the Browser's unprivileged URL
69
+ * surface.
70
+ */
71
+ kind: "url";
72
+ url: string;
73
+ title?: string;
74
+ }
75
+ | {
76
+ kind: "view";
77
+ viewId: string;
78
+ title?: string;
79
+ };
80
+
81
+ /**
82
+ * Authoring-time view reference. View IDs do not exist until provisioning, so
83
+ * templates and packages address a view by its stable authored name and/or
84
+ * slug. The pod resolves this to `WorkspacePrimarySurface` before persistence.
85
+ */
86
+ export type WorkspacePrimarySurfaceViewReference =
87
+ | {
88
+ kind: "view";
89
+ viewName: string;
90
+ viewSlug?: string;
91
+ title?: string;
92
+ }
93
+ | {
94
+ kind: "view";
95
+ viewName?: string;
96
+ viewSlug: string;
97
+ title?: string;
98
+ };
99
+
100
+ export type TemplatePrimarySurface =
101
+ | WorkspacePrimarySurface
102
+ | WorkspacePrimarySurfaceViewReference;
103
+
31
104
  // ============================================================================
32
105
  // Property / Profile / View (mirrors WorkspaceProposal from onboarding)
33
106
  // ============================================================================
@@ -53,8 +126,7 @@ export interface TemplateProfile {
53
126
  * Visibility of the profile DEFINITION — who can USE this vocabulary.
54
127
  * `system` = pod-wide; `shared` = ONE row reachable from other workspaces via
55
128
  * a `profile_workspace_access` grant; omit or `workspace` = this workspace
56
- * only. Orthogonal to `entityScope` below — do not conflate the two axes.
57
- */
129
+ * only. Orthogonal to `entityScope` below — do not conflate the two axes. */
58
130
  scope?: string;
59
131
  /**
60
132
  * Placement of the profile's ENTITY ROWS — where the data LANDS.
@@ -72,7 +144,7 @@ export interface TemplateProfile {
72
144
  * `entity_scope` of an existing profile (a divergence is reported as an
73
145
  * advisory `scopeConflict` and the live row is left alone).
74
146
  */
75
- entityScope?: 'pod' | 'workspace';
147
+ entityScope?: "pod" | "workspace";
76
148
  /** Semantic slug for system profiles. */
77
149
  semanticSlug?: string;
78
150
  /** Kind vs role profile. Omit for default ('kind'). */
@@ -139,22 +211,22 @@ export interface TemplateDisplayTemplate {
139
211
  // ============================================================================
140
212
 
141
213
  export interface TemplateSidebarSurface {
142
- kind: 'cell' | 'view' | 'entity' | 'document' | 'channel' | 'app' | 'url';
214
+ kind: "cell" | "view" | "entity" | "document" | "channel" | "app" | "url";
143
215
  cellKey?: string;
144
216
  viewId?: string;
145
217
  viewName?: string;
146
218
  appId?: string;
147
219
  url?: string;
148
- rendererType?: 'native' | 'external' | 'iframe-srcdoc';
149
- placement?: 'main' | 'side' | 'floating' | 'modal' | 'popover' | 'embed';
150
- displayMode?: 'compact' | 'medium' | 'full';
220
+ rendererType?: "native" | "external" | "iframe-srcdoc";
221
+ placement?: "main" | "side" | "floating" | "modal" | "popover" | "embed";
222
+ displayMode?: "compact" | "medium" | "full";
151
223
  props?: Record<string, unknown>;
152
224
  title?: string;
153
225
  meta?: Record<string, unknown>;
154
226
  }
155
227
 
156
228
  export interface TemplateSidebarItem {
157
- kind: 'app' | 'view' | 'profile' | 'external' | 'cell';
229
+ kind: "app" | "view" | "profile" | "external" | "cell";
158
230
  appId?: string;
159
231
  viewName?: string;
160
232
  viewId?: string;
@@ -173,7 +245,14 @@ export interface TemplateSidebarItem {
173
245
  export interface TemplateLayoutConfig {
174
246
  pinnedApps?: string[];
175
247
  defaultView?: string;
176
- /** The first-screen app — auto-opened on workspace switch */
248
+ /**
249
+ * Canonical first screen for this workspace.
250
+ *
251
+ * `undefined` means the template has no opinion during reconciliation,
252
+ * `null` explicitly returns the workspace to its home surface, and a
253
+ * descriptor replaces the current primary surface. */
254
+ primarySurface?: TemplatePrimarySurface | null;
255
+ /** Compatibility input only. New templates should use `app:` or `primarySurface`. */
177
256
  defaultApp?: string | null;
178
257
  theme?: string;
179
258
  sidebarItems?: TemplateSidebarItem[];
@@ -184,7 +263,7 @@ export interface TemplateLayoutConfig {
184
263
  // ============================================================================
185
264
 
186
265
  export interface TemplateBentoBlock {
187
- kind: 'view' | 'widget';
266
+ kind: "view" | "widget";
188
267
  viewName?: string;
189
268
  widgetType?: string;
190
269
  pos: { x: number; y: number; w: number; h: number };
@@ -203,7 +282,7 @@ export interface TemplateProfileEntityBentoTemplates {
203
282
  // ============================================================================
204
283
 
205
284
  /** Mirrors `WorkspaceSourceRole` in synap-backend `schema/workspaces.ts`. */
206
- export type TemplateSourceRole = 'provider' | 'consumer' | 'provider-consumer';
285
+ export type TemplateSourceRole = "provider" | "consumer" | "provider-consumer";
207
286
 
208
287
  /** Mirrors `WorkspaceDefaultSource` in synap-backend `schema/workspaces.ts`. */
209
288
  export interface TemplateDefaultSource {
@@ -233,7 +312,7 @@ export interface TemplateOnboardingCollectTarget {
233
312
  /** Human description of what to capture for this target. */
234
313
  what: string;
235
314
  /** Roughly how many to expect — guides interview depth. */
236
- cardinality?: 'one' | 'few' | 'several';
315
+ cardinality?: "one" | "few" | "several";
237
316
  /** Key fields the agent should make sure to fill. */
238
317
  keyFields?: string[];
239
318
  }
@@ -288,7 +367,7 @@ export interface TemplateOnboarding {
288
367
 
289
368
  export interface TemplatePlaybookParam {
290
369
  name: string;
291
- type: 'text' | 'number' | 'entity' | 'choice' | 'boolean';
370
+ type: "text" | "number" | "entity" | "choice" | "boolean";
292
371
  label?: string;
293
372
  required?: boolean;
294
373
  defaultValue?: unknown;
@@ -299,12 +378,12 @@ export interface TemplatePlaybook {
299
378
  description?: string;
300
379
  goalTemplate: string;
301
380
  params?: TemplatePlaybookParam[];
302
- executor?: 'is-agent' | 'external-agent' | 'hybrid';
303
- inputStrategy?: 'none' | 'static' | 'rotating' | 'query';
381
+ executor?: "is-agent" | "external-agent" | "hybrid";
382
+ inputStrategy?: "none" | "static" | "rotating" | "query";
304
383
  channelSpec?: {
305
- type: 'GROUP' | 'AGENT_COLLAB' | 'THREAD';
384
+ type: "GROUP" | "AGENT_COLLAB" | "THREAD";
306
385
  members?: string[];
307
- aiReactionMode?: 'on_mention' | 'always' | 'never';
386
+ aiReactionMode?: "on_mention" | "always" | "never";
308
387
  };
309
388
  /**
310
389
  * A cron schedule that maintains ONE backing cron automation (a single
@@ -330,7 +409,7 @@ export interface TemplatePlaybook {
330
409
  * captured entity of this kind can auto-launch the playbook as a session.
331
410
  */
332
411
  subjectProfile?: { profileSlug: string; filter?: Record<string, unknown> };
333
- status?: 'draft' | 'active' | 'paused';
412
+ status?: "draft" | "active" | "paused";
334
413
  }
335
414
 
336
415
  // ============================================================================
@@ -360,10 +439,10 @@ export interface TemplateAutomation {
360
439
  key?: string;
361
440
  name: string;
362
441
  description?: string;
363
- triggerType: 'event' | 'cron' | 'webhook' | 'manual';
442
+ triggerType: "event" | "cron" | "webhook" | "manual";
364
443
  triggerConfig?: Record<string, unknown>;
365
444
  flow: { nodes: unknown[]; edges: unknown[]; precondition?: string };
366
- status?: 'draft' | 'active' | 'paused';
445
+ status?: "draft" | "active" | "paused";
367
446
  }
368
447
 
369
448
  // ============================================================================
@@ -395,9 +474,9 @@ export interface TemplateActionPlacement {
395
474
  /** Entity kind whose detail surface this action appears on. */
396
475
  profileSlug: string;
397
476
  /** Where the action renders. Only "entity-detail" is consumed today. */
398
- surface: 'entity-detail' | string;
477
+ surface: "entity-detail" | string;
399
478
  /** What the action does when clicked. */
400
- kind: 'capability' | 'playbook' | 'automation';
479
+ kind: "capability" | "playbook" | "automation";
401
480
  /**
402
481
  * Reference to the thing to run: a capability verb key, or a playbook /
403
482
  * automation NAME (resolved to a row id at apply time).
@@ -440,7 +519,7 @@ export interface TemplateIntegration {
440
519
  /** Override default params with {{var}} interpolation. */
441
520
  params?: Record<string, string>;
442
521
  /** "apply" (create+update) or "install" (create only). */
443
- mode?: 'apply' | 'install';
522
+ mode?: "apply" | "install";
444
523
  }
445
524
 
446
525
  // ============================================================================
@@ -514,10 +593,10 @@ export interface TemplateExtendsRef {
514
593
  * 3. Once no payload carries `'automation'`, drop it from the wire enums and this tuple.
515
594
  */
516
595
  export const PACKAGE_DEPENDENCY_KINDS = [
517
- 'workspace',
518
- 'capability',
596
+ "workspace",
597
+ "capability",
519
598
  /** @deprecated Retired by CP 0049 in favour of `workflow`. See note above. */
520
- 'automation',
599
+ "automation",
521
600
  ] as const;
522
601
 
523
602
  /** What a `TemplateDependency` points at. Derived — never re-declare the list. */
@@ -541,7 +620,7 @@ export interface TemplateDependency {
541
620
  * - `'require'` (default): the dependency must be present on the pod as its
542
621
  * own artifact, but this template does not merge into it.
543
622
  */
544
- relation?: 'compose' | 'require';
623
+ relation?: "compose" | "require";
545
624
  /**
546
625
  * Short human reason shown in the install-time "this also installs…" prompt.
547
626
  */
@@ -585,7 +664,7 @@ export interface WorkspaceYaml {
585
664
  * Workspace classifier — consumed by createFromDefinition (settings.workspaceType,
586
665
  * creator-role + agent-workspace behavior). Mirrors the backend union.
587
666
  */
588
- workspaceType?: 'personal' | 'agent' | 'project' | 'operational';
667
+ workspaceType?: "personal" | "agent" | "project" | "operational";
589
668
  visibility?: string;
590
669
  /** Capabilities this workspace provides (e.g. "brand.library", "content.pipeline") */
591
670
  capabilities?: string[];
@@ -682,7 +761,7 @@ export interface WorkspaceYaml {
682
761
 
683
762
  export interface ResolvedWorkspaceTemplate {
684
763
  meta: TemplateMeta;
685
- definition: WorkspaceYaml['workspace'] & {
764
+ definition: WorkspaceYaml["workspace"] & {
686
765
  profiles: TemplateProfile[];
687
766
  views: TemplateView[];
688
767
  entityLinks: TemplateEntityLink[];