@synap-core/workspace-templates 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +247 -4
- package/dist/index.js +1762 -630
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/agent-fleet.yaml +16 -8
- package/src/catalog.test.ts +156 -0
- package/src/catalog.ts +285 -0
- package/src/communication.yaml +28 -1
- package/src/content-os.yaml +545 -0
- package/src/content-studio.yaml +0 -268
- package/src/crm.yaml +196 -0
- package/src/define.ts +268 -5
- package/src/enterprise-os.yaml +183 -0
- package/src/index.ts +27 -1
- package/src/marketing.yaml +14 -5
- package/src/templates.ts +1676 -656
- package/src/the-arch.yaml +182 -0
- package/src/types.ts +51 -1
package/src/define.ts
CHANGED
|
@@ -10,9 +10,107 @@
|
|
|
10
10
|
* shape never drifts per call-site.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { WorkspaceYaml } from "./types.js";
|
|
13
|
+
import type { TemplateBentoBlock, WorkspaceYaml } from "./types.js";
|
|
14
14
|
import { getWorkspaceTemplate } from "./templates.js";
|
|
15
15
|
|
|
16
|
+
/** A widget block as the wire expects it: `bentoLayout[]`, keyed by `widgetType`. */
|
|
17
|
+
export interface BentoWidgetBlockWire {
|
|
18
|
+
widgetType: string;
|
|
19
|
+
pos: TemplateBentoBlock['pos'];
|
|
20
|
+
config?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A view block as the wire expects it: `bentoViewBlocks[]`, keyed by `viewName`. */
|
|
24
|
+
export interface BentoViewBlockWire {
|
|
25
|
+
kind: 'view';
|
|
26
|
+
viewName: string;
|
|
27
|
+
pos: TemplateBentoBlock['pos'];
|
|
28
|
+
overrides?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SplitBentoBlocks {
|
|
32
|
+
bentoLayout: BentoWidgetBlockWire[];
|
|
33
|
+
bentoViewBlocks: BentoViewBlockWire[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Split the authored `bento.blocks` DISCRIMINATED UNION into the TWO wire
|
|
38
|
+
* fields the pod expects. This is the whole point: `TemplateBentoBlock` is one
|
|
39
|
+
* array discriminated by `kind`, but the pod does NOT read `kind` — it splits by
|
|
40
|
+
* FIELD (`create-workspace-from-definition.ts`):
|
|
41
|
+
*
|
|
42
|
+
* - `bentoLayout` → mapped as widgets, reads `widget.widgetType`
|
|
43
|
+
* - `bentoViewBlocks` → filtered against `viewMap[vb.viewSlug ?? vb.viewName]`,
|
|
44
|
+
* unknown entries DROPPED with a logger.warn
|
|
45
|
+
*
|
|
46
|
+
* Dumping the whole union into either single field silently mis-routes the other
|
|
47
|
+
* half — which is exactly what both converters used to do:
|
|
48
|
+
* - `toWorkspaceDefinition` sent all blocks as `bentoViewBlocks`, so widgets
|
|
49
|
+
* (no `viewName`) hit `viewMap[undefined]` and were filtered out.
|
|
50
|
+
* - `toPackageDefinition` sent all blocks as `bentoLayout`, so view blocks
|
|
51
|
+
* (no `widgetType`) became widgets with `widgetType: undefined`.
|
|
52
|
+
* Net: nobody had ever seen an authored bento render as authored.
|
|
53
|
+
*
|
|
54
|
+
* Blocks are emitted in the EXACT shape each sink declares (the tRPC
|
|
55
|
+
* `createFromDefinition` zod is strict: `bentoLayout` requires `widgetType`,
|
|
56
|
+
* `bentoViewBlocks` requires `kind: 'view'` + `viewName`), so a widget's
|
|
57
|
+
* `overrides` and a view's `config` are not smuggled into the wrong sink.
|
|
58
|
+
* Authored order is preserved WITHIN each kind; the pod concatenates
|
|
59
|
+
* `[...widgetBlocks, ...viewBlocks]`, so cross-kind order is the pod's to define.
|
|
60
|
+
*
|
|
61
|
+
* `kind` is INFERRED from field presence when absent. `TemplateBentoBlock.kind`
|
|
62
|
+
* is a required field in TypeScript, but the generated `templates.ts` casts the
|
|
63
|
+
* parsed YAML `as unknown as Record<string, WorkspaceYaml>` — so the compiler
|
|
64
|
+
* never checks the authored data against the type, and `agent-fleet.yaml`
|
|
65
|
+
* authored 8 widget blocks with no `kind:` at all (bare `widgetType`). Routing
|
|
66
|
+
* strictly on `kind` would silently DROP those — the exact failure mode this
|
|
67
|
+
* function exists to end. Inferring by field mirrors what the pod itself does
|
|
68
|
+
* (it splits by field and never reads `kind`), so the seam is total: a block
|
|
69
|
+
* that CAN be materialized always is. `scripts/check-converters.mjs` is the
|
|
70
|
+
* strict half — it fails the build if an authored block omits `kind` — so this
|
|
71
|
+
* leniency is a safety net, not a place for drift to hide.
|
|
72
|
+
*/
|
|
73
|
+
function resolveBlockKind(block: TemplateBentoBlock): 'widget' | 'view' | null {
|
|
74
|
+
if (block.kind === 'widget' || block.kind === 'view') return block.kind;
|
|
75
|
+
if (block.widgetType) return 'widget';
|
|
76
|
+
if (block.viewName) return 'view';
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function splitBentoBlocks(
|
|
81
|
+
blocks: TemplateBentoBlock[] | undefined
|
|
82
|
+
): SplitBentoBlocks {
|
|
83
|
+
const bentoLayout: BentoWidgetBlockWire[] = [];
|
|
84
|
+
const bentoViewBlocks: BentoViewBlockWire[] = [];
|
|
85
|
+
|
|
86
|
+
for (const block of blocks ?? []) {
|
|
87
|
+
const kind = resolveBlockKind(block);
|
|
88
|
+
if (kind === 'widget') {
|
|
89
|
+
// A widget block without `widgetType` cannot be rendered — the pod would
|
|
90
|
+
// create a `widgetType: undefined` block. Drop it loudly rather than ship
|
|
91
|
+
// a broken block; the build-time guard asserts this never happens in the
|
|
92
|
+
// authored templates.
|
|
93
|
+
if (!block.widgetType) continue;
|
|
94
|
+
bentoLayout.push({
|
|
95
|
+
widgetType: block.widgetType,
|
|
96
|
+
pos: block.pos,
|
|
97
|
+
...(block.config ? { config: block.config } : {}),
|
|
98
|
+
});
|
|
99
|
+
} else if (kind === 'view') {
|
|
100
|
+
// Same reasoning: the pod filters unknown/absent view names anyway.
|
|
101
|
+
if (!block.viewName) continue;
|
|
102
|
+
bentoViewBlocks.push({
|
|
103
|
+
kind: 'view',
|
|
104
|
+
viewName: block.viewName,
|
|
105
|
+
pos: block.pos,
|
|
106
|
+
...(block.overrides ? { overrides: block.overrides } : {}),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { bentoLayout, bentoViewBlocks };
|
|
112
|
+
}
|
|
113
|
+
|
|
16
114
|
/** The flat definition object accepted by createFromDefinition. */
|
|
17
115
|
export interface WorkspaceDefinitionInput {
|
|
18
116
|
workspaceName: string;
|
|
@@ -26,7 +124,16 @@ export interface WorkspaceDefinitionInput {
|
|
|
26
124
|
displayTemplates?: WorkspaceYaml['displayTemplates'];
|
|
27
125
|
layoutConfig: WorkspaceYaml['layout'] | undefined;
|
|
28
126
|
bentoViewName?: string;
|
|
29
|
-
|
|
127
|
+
/**
|
|
128
|
+
* WIDGET half of the authored bento union. The door declares this field
|
|
129
|
+
* (`workspaces.createFromDefinition` zod) and the pod maps it as widgets via
|
|
130
|
+
* `widget.widgetType`. It was missing from this interface entirely, which is
|
|
131
|
+
* why `toWorkspaceDefinition` had nowhere to put widgets and dumped the whole
|
|
132
|
+
* union into `bentoViewBlocks` — where every widget was silently filtered out.
|
|
133
|
+
*/
|
|
134
|
+
bentoLayout?: BentoWidgetBlockWire[];
|
|
135
|
+
/** VIEW half of the authored bento union — resolved against the viewMap. */
|
|
136
|
+
bentoViewBlocks?: BentoViewBlockWire[];
|
|
30
137
|
profileEntityBentoTemplates?: WorkspaceYaml['profileEntityBentoTemplates'];
|
|
31
138
|
// Workspace-level metadata (flattened from the `workspace` block)
|
|
32
139
|
workspaceSubtype?: string;
|
|
@@ -77,6 +184,10 @@ export function toWorkspaceDefinition(
|
|
|
77
184
|
const name = nameOverride ?? ws.name;
|
|
78
185
|
const proposalId = ws.proposalId ?? `${tpl.meta.slug}-v1`;
|
|
79
186
|
|
|
187
|
+
// The authored `bento.blocks` union must be ROUTED, not forwarded: the pod
|
|
188
|
+
// splits by field, not by `kind`. See `splitBentoBlocks`.
|
|
189
|
+
const bento = splitBentoBlocks(tpl.bento?.blocks);
|
|
190
|
+
|
|
80
191
|
const definition: WorkspaceDefinitionInput = {
|
|
81
192
|
workspaceName: name,
|
|
82
193
|
description: ws.description,
|
|
@@ -89,7 +200,8 @@ export function toWorkspaceDefinition(
|
|
|
89
200
|
displayTemplates: tpl.displayTemplates,
|
|
90
201
|
layoutConfig: tpl.layout,
|
|
91
202
|
bentoViewName: tpl.bento?.viewName,
|
|
92
|
-
|
|
203
|
+
bentoLayout: bento.bentoLayout,
|
|
204
|
+
bentoViewBlocks: bento.bentoViewBlocks,
|
|
93
205
|
profileEntityBentoTemplates: tpl.profileEntityBentoTemplates,
|
|
94
206
|
workspaceSubtype: ws.subtype,
|
|
95
207
|
workspaceType: ws.workspaceType,
|
|
@@ -114,6 +226,90 @@ export function toWorkspaceDefinition(
|
|
|
114
226
|
return { definition, proposalId, workspaceName: name, templateId: tpl.meta.slug };
|
|
115
227
|
}
|
|
116
228
|
|
|
229
|
+
// ─── Exhaustiveness ledger: toWorkspaceDefinition ────────────────────────────
|
|
230
|
+
// `WorkspaceYaml` is read by two converters and nothing forced either to be
|
|
231
|
+
// total — which is how `dependencies` was silently dropped here for months.
|
|
232
|
+
// Every key of `WorkspaceYaml` must be classified below as either FORWARDED
|
|
233
|
+
// onto the wire or explicitly DROPPED with a reason. Adding a field to
|
|
234
|
+
// `WorkspaceYaml` FAILS THE BUILD until it is classified, forcing the author
|
|
235
|
+
// to make the forward-or-drop decision instead of defaulting to silent loss.
|
|
236
|
+
//
|
|
237
|
+
// ⚠️ KNOW WHAT THIS LEDGER DOES NOT CATCH. It is FIELD-PRESENCE-exhaustive, not
|
|
238
|
+
// SEMANTICS-exhaustive. It asks "is every key classified?" — not "does the value
|
|
239
|
+
// reach the right sink?". `bento` was classified as forwarded, and it WAS
|
|
240
|
+
// forwarded: the whole discriminated union was dumped into ONE field on each
|
|
241
|
+
// door (`bentoViewBlocks` here, `bentoLayout` in `toPackageDefinition`). The
|
|
242
|
+
// ledger passed while widgets were filtered out on one door and views became
|
|
243
|
+
// `widgetType: undefined` widgets on the other. A ledger entry is a CLAIM about
|
|
244
|
+
// routing; only a runtime assertion can check the claim.
|
|
245
|
+
// → The counterpart guard for that class is `scripts/check-converters.mjs`,
|
|
246
|
+
// which RUNS both converters over all templates and asserts the per-kind
|
|
247
|
+
// block counts survive the split. Any future union-shaped field (one authored
|
|
248
|
+
// array fanning out to N wire fields) needs an assertion there too — do not
|
|
249
|
+
// let a ledger comment stand in for one.
|
|
250
|
+
|
|
251
|
+
/** Keys `toWorkspaceDefinition` reads and carries onto the wire. */
|
|
252
|
+
type WorkspaceDefinitionForwarded =
|
|
253
|
+
| 'meta' // → proposalId (meta.slug) + templateId; not a wire field itself
|
|
254
|
+
| 'workspace' // flattened into workspaceName/description/workspaceType/…
|
|
255
|
+
| 'profiles'
|
|
256
|
+
| 'views'
|
|
257
|
+
| 'entityLinks'
|
|
258
|
+
| 'seedEntities'
|
|
259
|
+
| 'suggestedRelations'
|
|
260
|
+
| 'displayTemplates'
|
|
261
|
+
| 'layout' // → layoutConfig
|
|
262
|
+
| 'bento' // → bentoViewName + bentoLayout (widgets) + bentoViewBlocks (views)
|
|
263
|
+
| 'profileEntityBentoTemplates'
|
|
264
|
+
| 'onboarding'
|
|
265
|
+
| 'dependencies';
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Keys deliberately NOT forwarded by `toWorkspaceDefinition`. Each needs a
|
|
269
|
+
* one-line WHY. "The door has no such field" = forwarding is a no-op; anything
|
|
270
|
+
* else is a real gap and should say so.
|
|
271
|
+
*/
|
|
272
|
+
type WorkspaceDefinitionDropped =
|
|
273
|
+
// UI-only: `workspaces.createFromDefinition` has no `app` field. Consumers
|
|
274
|
+
// read `template.app` straight off the YAML to pick the first screen, so this
|
|
275
|
+
// never belonged on the wire.
|
|
276
|
+
| 'app'
|
|
277
|
+
// The door DOES accept `extends`, but no template declares it today (0/21).
|
|
278
|
+
// Forwarding is a no-op until one does — wire it then.
|
|
279
|
+
| 'extends'
|
|
280
|
+
// The door DOES accept `automations`, but no template declares it today
|
|
281
|
+
// (0/21), and its zod shape ({trigger,action:{playbook_run}}) is not
|
|
282
|
+
// `TemplateAutomation`. Needs a mapper if a template ever uses it.
|
|
283
|
+
// The browser install path installs automations via its own createAutomation door.
|
|
284
|
+
| 'automations'
|
|
285
|
+
// The door has no `integrations` field. Its `tools` field is a DIFFERENT
|
|
286
|
+
// shape ({name,kind,inputSchema} vs {templateKey,params,mode}). Capability
|
|
287
|
+
// templates are seeded via the packages/apply path, not createFromDefinition
|
|
288
|
+
// — see the note on `TemplateIntegration` in types.ts. Forwarding = no-op.
|
|
289
|
+
| 'integrations'
|
|
290
|
+
// REAL GAP (agent-fleet, project-management declare playbooks). The door
|
|
291
|
+
// accepts `playbooks`, but the shapes disagree: template param `type` allows
|
|
292
|
+
// 'text'|'entity'|'choice', the tRPC enum accepts only string|number|boolean
|
|
293
|
+
// (and defaultValue↔default, grants: string[]↔{kind,ref}[]). Raw forwarding =
|
|
294
|
+
// zod BAD_REQUEST. Needs a param mapper — separate wave, not a silent drop.
|
|
295
|
+
| 'playbooks';
|
|
296
|
+
|
|
297
|
+
type UnclassifiedWorkspaceDefinitionKey = Exclude<
|
|
298
|
+
keyof WorkspaceYaml,
|
|
299
|
+
WorkspaceDefinitionForwarded | WorkspaceDefinitionDropped
|
|
300
|
+
>;
|
|
301
|
+
|
|
302
|
+
// If this errors, a new `WorkspaceYaml` field is unclassified. Forward it in
|
|
303
|
+
// `toWorkspaceDefinition` and add it to `WorkspaceDefinitionForwarded`, or add
|
|
304
|
+
// it to `WorkspaceDefinitionDropped` with a one-line WHY.
|
|
305
|
+
const _workspaceDefinitionLedger: [UnclassifiedWorkspaceDefinitionKey] extends [never]
|
|
306
|
+
? true
|
|
307
|
+
: [
|
|
308
|
+
'UNCLASSIFIED YAML FIELD — forward it or add it to WorkspaceDefinitionDropped:',
|
|
309
|
+
UnclassifiedWorkspaceDefinitionKey,
|
|
310
|
+
] = true;
|
|
311
|
+
void _workspaceDefinitionLedger;
|
|
312
|
+
|
|
117
313
|
/**
|
|
118
314
|
* The unified `PackageDefinition` shape consumed by `POST /api/hub/packages/apply`
|
|
119
315
|
* (CLI `synap launch`) and the CP package registry seeder. It composes the
|
|
@@ -145,7 +341,17 @@ export interface PackageDefinitionOutput {
|
|
|
145
341
|
suggestedRelations?: WorkspaceYaml['suggestedRelations'];
|
|
146
342
|
displayTemplates?: WorkspaceYaml['displayTemplates'];
|
|
147
343
|
entityLinks?: WorkspaceYaml['entityLinks'];
|
|
148
|
-
|
|
344
|
+
/** WIDGET half of the authored bento union — mapped by the pod via `widgetType`. */
|
|
345
|
+
bentoLayout?: BentoWidgetBlockWire[];
|
|
346
|
+
/**
|
|
347
|
+
* VIEW half of the authored bento union. Widgets go to `bentoLayout`, views
|
|
348
|
+
* here — the pod splits by field (create-workspace-from-definition.ts:1373
|
|
349
|
+
* reads `bentoViewBlocks`), so both wire fields must be carried. The apply
|
|
350
|
+
* door's `PackageApplySchema` now declares this field (it is a plain
|
|
351
|
+
* `z.object`, so an undeclared field would be silently stripped) — the inbound
|
|
352
|
+
* seam is closed to match this outbound emit.
|
|
353
|
+
*/
|
|
354
|
+
bentoViewBlocks?: BentoViewBlockWire[];
|
|
149
355
|
bentoViewName?: string;
|
|
150
356
|
profileEntityBentoTemplates?: WorkspaceYaml['profileEntityBentoTemplates'];
|
|
151
357
|
layoutConfig?: WorkspaceYaml['layout'];
|
|
@@ -173,6 +379,9 @@ export function toPackageDefinition(
|
|
|
173
379
|
}
|
|
174
380
|
|
|
175
381
|
const ws = tpl.workspace;
|
|
382
|
+
// Same routing as the tRPC door — the union is split by `kind`, never dumped
|
|
383
|
+
// whole into one field. See `splitBentoBlocks`.
|
|
384
|
+
const bento = splitBentoBlocks(tpl.bento?.blocks);
|
|
176
385
|
return {
|
|
177
386
|
_meta: {
|
|
178
387
|
slug: tpl.meta.slug,
|
|
@@ -195,7 +404,8 @@ export function toPackageDefinition(
|
|
|
195
404
|
suggestedRelations: tpl.suggestedRelations,
|
|
196
405
|
displayTemplates: tpl.displayTemplates,
|
|
197
406
|
entityLinks: tpl.entityLinks,
|
|
198
|
-
bentoLayout:
|
|
407
|
+
bentoLayout: bento.bentoLayout,
|
|
408
|
+
bentoViewBlocks: bento.bentoViewBlocks,
|
|
199
409
|
bentoViewName: tpl.bento?.viewName,
|
|
200
410
|
profileEntityBentoTemplates: tpl.profileEntityBentoTemplates,
|
|
201
411
|
layoutConfig: tpl.layout,
|
|
@@ -205,3 +415,56 @@ export function toPackageDefinition(
|
|
|
205
415
|
dependencies: tpl.dependencies,
|
|
206
416
|
};
|
|
207
417
|
}
|
|
418
|
+
|
|
419
|
+
// ─── Exhaustiveness ledger: toPackageDefinition ──────────────────────────────
|
|
420
|
+
// Same forcing function as above, for the OTHER converter. These two are NOT
|
|
421
|
+
// merged on purpose: they target different endpoints with different contracts
|
|
422
|
+
// (`PackageDefinition` is a superset with renames — seedEntities→suggestedEntities,
|
|
423
|
+
// bento.blocks→bentoLayout, integrations→capabilities, plus `_meta`). Keeping
|
|
424
|
+
// two converters is fine; keeping either one lossy-by-accident is not.
|
|
425
|
+
|
|
426
|
+
/** Keys `toPackageDefinition` reads and carries onto the wire. */
|
|
427
|
+
type PackageDefinitionForwarded =
|
|
428
|
+
| 'meta' // → _meta
|
|
429
|
+
| 'workspace' // flattened into workspaceName/description/workspaceType/…
|
|
430
|
+
| 'profiles'
|
|
431
|
+
| 'views'
|
|
432
|
+
| 'seedEntities' // → suggestedEntities
|
|
433
|
+
| 'suggestedRelations'
|
|
434
|
+
| 'displayTemplates'
|
|
435
|
+
| 'entityLinks'
|
|
436
|
+
| 'bento' // → bentoViewName + bentoLayout (widgets) + bentoViewBlocks (views)
|
|
437
|
+
| 'profileEntityBentoTemplates'
|
|
438
|
+
| 'layout' // → layoutConfig
|
|
439
|
+
| 'integrations' // → capabilities
|
|
440
|
+
| 'playbooks'
|
|
441
|
+
| 'onboarding'
|
|
442
|
+
| 'dependencies';
|
|
443
|
+
|
|
444
|
+
/** Keys deliberately NOT forwarded by `toPackageDefinition`. */
|
|
445
|
+
type PackageDefinitionDropped =
|
|
446
|
+
// UI-only, same as the definition door: `PackageDefinition` has no `app`.
|
|
447
|
+
| 'app'
|
|
448
|
+
// REAL GAP: backend `PackageDefinition` DOES declare `extends`
|
|
449
|
+
// (package-definition.ts:262) and `automations` (:268), but
|
|
450
|
+
// `PackageDefinitionOutput` omits both. No template declares either today
|
|
451
|
+
// (0/21), so this is latent, not live — wire them when a template first uses
|
|
452
|
+
// one.
|
|
453
|
+
| 'extends'
|
|
454
|
+
| 'automations';
|
|
455
|
+
|
|
456
|
+
type UnclassifiedPackageDefinitionKey = Exclude<
|
|
457
|
+
keyof WorkspaceYaml,
|
|
458
|
+
PackageDefinitionForwarded | PackageDefinitionDropped
|
|
459
|
+
>;
|
|
460
|
+
|
|
461
|
+
// If this errors, a new `WorkspaceYaml` field is unclassified. Forward it in
|
|
462
|
+
// `toPackageDefinition` and add it to `PackageDefinitionForwarded`, or add it
|
|
463
|
+
// to `PackageDefinitionDropped` with a one-line WHY.
|
|
464
|
+
const _packageDefinitionLedger: [UnclassifiedPackageDefinitionKey] extends [never]
|
|
465
|
+
? true
|
|
466
|
+
: [
|
|
467
|
+
'UNCLASSIFIED YAML FIELD — forward it or add it to PackageDefinitionDropped:',
|
|
468
|
+
UnclassifiedPackageDefinitionKey,
|
|
469
|
+
] = true;
|
|
470
|
+
void _packageDefinitionLedger;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Enterprise OS — the public operating system suite
|
|
2
|
+
# ==================================================
|
|
3
|
+
# A SUITE template: it stands up a whole company's public operating core in one
|
|
4
|
+
# install by REQUIRING its constituent workspaces (deps-first), then adds a thin
|
|
5
|
+
# "command tower" home of its own — a place to track the company's objectives
|
|
6
|
+
# across the lenses it just installed.
|
|
7
|
+
#
|
|
8
|
+
# What it composes (all public, all bundled):
|
|
9
|
+
# • foundation — strategic DNA (mission, audience, positioning, principles)
|
|
10
|
+
# • ecosystem — the market graph (partners, competitors, segments, trends)
|
|
11
|
+
# • operations — how the company runs (the operational lens)
|
|
12
|
+
# • crm — the pipeline (contacts, leads, clients, deals)
|
|
13
|
+
#
|
|
14
|
+
# It uses `require` (not `compose`): each constituent is a REAL workspace, not an
|
|
15
|
+
# overlay. Deps-first resolution installs foundation first (the SSOT role
|
|
16
|
+
# vocabulary provider), so every downstream lens reuses foundation's pod-wide
|
|
17
|
+
# roles rather than forking twins. Installing Enterprise OS a second time is
|
|
18
|
+
# idempotent — the resolver reuses existing constituent workspaces by slug.
|
|
19
|
+
|
|
20
|
+
meta:
|
|
21
|
+
slug: enterprise-os
|
|
22
|
+
name: Enterprise OS
|
|
23
|
+
description: Stand up a company's public operating core in one install — strategy, market, operations, and pipeline — plus a command tower to track objectives across them.
|
|
24
|
+
icon: layout-dashboard
|
|
25
|
+
color: "#4F46E5"
|
|
26
|
+
tags: [suite, enterprise, os, foundation, crm, operations, ecosystem]
|
|
27
|
+
isPublic: true
|
|
28
|
+
|
|
29
|
+
# Deps-first: foundation is the pod-wide role-vocabulary provider, so it must
|
|
30
|
+
# resolve before ecosystem/operations/crm reuse its shared roles. `require` (not
|
|
31
|
+
# `compose`): each is a standalone workspace, not an additive overlay.
|
|
32
|
+
dependencies:
|
|
33
|
+
- slug: foundation
|
|
34
|
+
kind: workspace
|
|
35
|
+
relation: require
|
|
36
|
+
reason: "Strategic DNA + the pod-wide role vocabulary (partner/client/lead) every other lens reuses. Must resolve first."
|
|
37
|
+
- slug: ecosystem
|
|
38
|
+
kind: workspace
|
|
39
|
+
relation: require
|
|
40
|
+
reason: "The market graph — the players, segments, and trends the company operates around."
|
|
41
|
+
- slug: operations
|
|
42
|
+
kind: workspace
|
|
43
|
+
relation: require
|
|
44
|
+
reason: "How the company runs — the operational lens."
|
|
45
|
+
- slug: crm
|
|
46
|
+
kind: workspace
|
|
47
|
+
relation: require
|
|
48
|
+
reason: "The pipeline — contacts, leads, clients, and deals."
|
|
49
|
+
|
|
50
|
+
workspace:
|
|
51
|
+
name: Enterprise OS
|
|
52
|
+
description: The command tower for the company operating system — track strategic objectives across the strategy, market, operations, and pipeline lenses this suite installs.
|
|
53
|
+
subtype: enterprise-os
|
|
54
|
+
visibility: pod_visible
|
|
55
|
+
sourceRoles:
|
|
56
|
+
strategy: consumer
|
|
57
|
+
|
|
58
|
+
profiles:
|
|
59
|
+
# The one native profile the command tower owns: a strategic objective the
|
|
60
|
+
# company is driving across the lenses this suite installs. Deliberately light
|
|
61
|
+
# — the depth lives in the constituent workspaces; this is the cross-cutting
|
|
62
|
+
# scoreboard that ties them together.
|
|
63
|
+
- slug: objective
|
|
64
|
+
displayName: Objective
|
|
65
|
+
icon: target
|
|
66
|
+
color: "#4F46E5"
|
|
67
|
+
description: A company objective the operating system is driving toward — the cross-cutting outcome the lenses serve.
|
|
68
|
+
properties:
|
|
69
|
+
- slug: outcome
|
|
70
|
+
label: Outcome
|
|
71
|
+
valueType: string
|
|
72
|
+
inputType: textarea
|
|
73
|
+
- slug: status
|
|
74
|
+
label: Status
|
|
75
|
+
valueType: string
|
|
76
|
+
inputType: select
|
|
77
|
+
enumValues: [on-track, at-risk, off-track, achieved]
|
|
78
|
+
constraints: { defaultValue: on-track }
|
|
79
|
+
- slug: horizon
|
|
80
|
+
label: Horizon
|
|
81
|
+
valueType: string
|
|
82
|
+
inputType: select
|
|
83
|
+
enumValues: [now, next, later]
|
|
84
|
+
constraints: { defaultValue: now }
|
|
85
|
+
- slug: owner
|
|
86
|
+
label: Owner
|
|
87
|
+
valueType: string
|
|
88
|
+
inputType: text
|
|
89
|
+
- slug: targetDate
|
|
90
|
+
label: Target Date
|
|
91
|
+
valueType: date
|
|
92
|
+
inputType: date
|
|
93
|
+
|
|
94
|
+
views:
|
|
95
|
+
- name: Objectives
|
|
96
|
+
slug: objectives
|
|
97
|
+
type: table
|
|
98
|
+
scopeProfileSlug: objective
|
|
99
|
+
description: The company's strategic objectives across every lens, by horizon and status.
|
|
100
|
+
defaultView: true
|
|
101
|
+
config:
|
|
102
|
+
columns: [title, status, horizon, owner, targetDate]
|
|
103
|
+
- name: Objective Board
|
|
104
|
+
slug: objective-board
|
|
105
|
+
type: kanban
|
|
106
|
+
scopeProfileSlug: objective
|
|
107
|
+
description: Objectives grouped by status — a running scoreboard for the operating system.
|
|
108
|
+
config:
|
|
109
|
+
groupBy: status
|
|
110
|
+
|
|
111
|
+
onboarding:
|
|
112
|
+
goal: >
|
|
113
|
+
Stand up the company's operating core and name the handful of strategic
|
|
114
|
+
objectives it is driving — so strategy, market, operations, and pipeline are
|
|
115
|
+
all in place and pointed at the same outcomes from day one.
|
|
116
|
+
framing: |
|
|
117
|
+
THE CHIEF OF STAFF: Act as a sharp chief of staff standing up the company OS.
|
|
118
|
+
The four lenses (foundation, ecosystem, operations, CRM) are already
|
|
119
|
+
installed — your job is to name the 3-5 objectives that cut across them, so
|
|
120
|
+
the operating system has a scoreboard, not just structure.
|
|
121
|
+
expertise:
|
|
122
|
+
starters:
|
|
123
|
+
- >
|
|
124
|
+
Name few, real objectives. An operating system with 20 objectives has
|
|
125
|
+
none; force the 3-5 that actually decide the year, each with an owner and
|
|
126
|
+
a horizon (now / next / later).
|
|
127
|
+
- >
|
|
128
|
+
Tie each objective to a lens. A good objective names where it lives —
|
|
129
|
+
pipeline growth (CRM), a market move (ecosystem), an operational fix
|
|
130
|
+
(operations), or a positioning shift (foundation) — so the scoreboard
|
|
131
|
+
maps to the structure underneath it.
|
|
132
|
+
blindSpots:
|
|
133
|
+
- >
|
|
134
|
+
Objectives with no owner and no date are wishes. Every one needs a single
|
|
135
|
+
owner and a target date, or it will not move.
|
|
136
|
+
- >
|
|
137
|
+
"Grow the business" is not an objective — it is a category. Push for the
|
|
138
|
+
specific outcome that would make this quarter a win.
|
|
139
|
+
bar: >
|
|
140
|
+
A short, honest scoreboard: 3-5 objectives, each owned, dated, placed on a
|
|
141
|
+
horizon, and tied to the lens that serves it — sitting on top of a fully
|
|
142
|
+
installed operating core.
|
|
143
|
+
collect:
|
|
144
|
+
- profileSlug: objective
|
|
145
|
+
what: The 3-5 strategic objectives the company is driving this horizon.
|
|
146
|
+
cardinality: few
|
|
147
|
+
keyFields: [status, horizon, owner]
|
|
148
|
+
openingQuestions:
|
|
149
|
+
- "What are the 3-5 objectives that will decide this year for the company?"
|
|
150
|
+
- "Who owns each one, and is it a now, next, or later horizon?"
|
|
151
|
+
doneWhen: A few objectives captured, each with an owner and a horizon.
|
|
152
|
+
|
|
153
|
+
app:
|
|
154
|
+
kind: stored
|
|
155
|
+
cellKey: entity-detail
|
|
156
|
+
|
|
157
|
+
layout:
|
|
158
|
+
defaultView: Objectives
|
|
159
|
+
defaultApp: null
|
|
160
|
+
pinnedApps: [dashboard, data]
|
|
161
|
+
sidebarItems:
|
|
162
|
+
- kind: view
|
|
163
|
+
viewName: Objectives
|
|
164
|
+
label: Objectives
|
|
165
|
+
icon: target
|
|
166
|
+
- kind: view
|
|
167
|
+
viewName: Objective Board
|
|
168
|
+
label: Board
|
|
169
|
+
icon: kanban-square
|
|
170
|
+
- kind: app
|
|
171
|
+
appId: data
|
|
172
|
+
label: Data
|
|
173
|
+
icon: database
|
|
174
|
+
|
|
175
|
+
bento:
|
|
176
|
+
viewName: Objectives
|
|
177
|
+
blocks:
|
|
178
|
+
- kind: view
|
|
179
|
+
viewName: Objectives
|
|
180
|
+
pos: { x: 0, y: 0, w: 8, h: 4 }
|
|
181
|
+
- kind: view
|
|
182
|
+
viewName: Objective Board
|
|
183
|
+
pos: { x: 8, y: 0, w: 4, h: 4 }
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type {
|
|
|
37
37
|
TemplateAutomation,
|
|
38
38
|
TemplateExtendsRef,
|
|
39
39
|
TemplateDependency,
|
|
40
|
+
PackageDependencyKind,
|
|
40
41
|
TemplateProfileEntityBentoTemplates,
|
|
41
42
|
TemplateDefaultSource,
|
|
42
43
|
TemplateSourceRole,
|
|
@@ -46,6 +47,12 @@ export type {
|
|
|
46
47
|
TemplatePlaybookParam,
|
|
47
48
|
} from "./types.js";
|
|
48
49
|
|
|
50
|
+
// The dependency-kind vocabulary as a VALUE — so consumers (marketplace,
|
|
51
|
+
// onboarding, backend, hub zod, browser, CP zod) can `z.enum(PACKAGE_DEPENDENCY_KINDS)`
|
|
52
|
+
// / derive their type from it instead of hand-copying the list. Six mirrors
|
|
53
|
+
// exist today; this is the door that lets them stop being mirrors.
|
|
54
|
+
export { PACKAGE_DEPENDENCY_KINDS } from "./types.js";
|
|
55
|
+
|
|
49
56
|
export {
|
|
50
57
|
WORKSPACE_TEMPLATES,
|
|
51
58
|
getWorkspaceTemplate,
|
|
@@ -55,9 +62,28 @@ export {
|
|
|
55
62
|
|
|
56
63
|
export type { WorkspaceTemplateSlug } from "./templates.js";
|
|
57
64
|
|
|
58
|
-
export {
|
|
65
|
+
export { mergeCatalog } from "./catalog.js";
|
|
66
|
+
export type {
|
|
67
|
+
MergeCatalogInput,
|
|
68
|
+
MergeCatalogResult,
|
|
69
|
+
BundledEntry,
|
|
70
|
+
RemoteEntry,
|
|
71
|
+
RemoteSourceStatus,
|
|
72
|
+
CatalogEntry,
|
|
73
|
+
BundledCatalogEntry,
|
|
74
|
+
RemoteCatalogEntry,
|
|
75
|
+
} from "./catalog.js";
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
toWorkspaceDefinition,
|
|
79
|
+
toPackageDefinition,
|
|
80
|
+
splitBentoBlocks,
|
|
81
|
+
} from "./define.js";
|
|
59
82
|
export type {
|
|
60
83
|
WorkspaceDefinitionInput,
|
|
61
84
|
ToDefinitionResult,
|
|
62
85
|
PackageDefinitionOutput,
|
|
86
|
+
SplitBentoBlocks,
|
|
87
|
+
BentoWidgetBlockWire,
|
|
88
|
+
BentoViewBlockWire,
|
|
63
89
|
} from "./define.js";
|
package/src/marketing.yaml
CHANGED
|
@@ -146,16 +146,25 @@ profiles:
|
|
|
146
146
|
# foundation already owns (e.g. `leadStage`) with a divergent enum/default —
|
|
147
147
|
# that is base drift, and the SSOT tripwire fails on it.
|
|
148
148
|
# (Standards reference inherited base props like `leadStage` directly.)
|
|
149
|
+
#
|
|
150
|
+
# The same reasoning applies to the ROW-LEVEL fields, and more strictly: the
|
|
151
|
+
# row has no overlay mechanism, so this template must not restate what
|
|
152
|
+
# foundation owns. `description`, `semanticSlug` and `applicableKinds` are
|
|
153
|
+
# therefore OMITTED here — they are written only on the create path, which
|
|
154
|
+
# foundation always takes, so declaring them here asserted a value that never
|
|
155
|
+
# applied (this template claimed `applicableKinds: [person]` while the live row
|
|
156
|
+
# says [company, person]).
|
|
157
|
+
# `icon`/`color` are the exception that must be RESTATED rather than dropped:
|
|
158
|
+
# they are also read into `profileHintsMap` to build THIS workspace's auto-bento
|
|
159
|
+
# for `lead`, so omitting them regresses the widgets to a default indigo
|
|
160
|
+
# database icon. They must MATCH foundation's row (`user-plus`/`yellow`) — the
|
|
161
|
+
# old `green` rendered green widgets around a yellow row.
|
|
149
162
|
- slug: lead
|
|
150
163
|
displayName: Lead
|
|
151
164
|
icon: user-plus
|
|
152
|
-
color:
|
|
153
|
-
description: "A potential customer or partner contact in your pipeline"
|
|
154
|
-
semanticSlug: marketing.lead
|
|
165
|
+
color: yellow
|
|
155
166
|
scope: shared
|
|
156
167
|
profileKind: role
|
|
157
|
-
applicableKinds:
|
|
158
|
-
- person
|
|
159
168
|
properties:
|
|
160
169
|
- slug: lead-source
|
|
161
170
|
label: Source
|