@synap-core/workspace-templates 0.5.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 +734 -17
- package/dist/index.js +25031 -20
- package/dist/index.js.map +1 -0
- package/package.json +7 -5
- package/src/agent-fleet.yaml +16 -8
- package/src/blockchain-ecosystem.yaml +372 -0
- package/src/catalog.test.ts +156 -0
- package/src/catalog.ts +285 -0
- package/src/communication.yaml +689 -0
- package/src/content-os.yaml +252 -771
- package/src/crm.yaml +181 -2071
- package/src/define.ts +292 -6
- package/src/dev-dashboard.yaml +3 -0
- package/src/ecosystem.yaml +478 -0
- package/src/enterprise-os.yaml +183 -0
- package/src/foundation.yaml +226 -0
- package/src/grants.yaml +1096 -0
- package/src/hr.yaml +6 -0
- package/src/index.ts +32 -5
- package/src/internal-runbook.yaml +499 -0
- package/src/legal.yaml +6 -6
- package/src/marketing.yaml +48 -12
- package/src/operations.yaml +146 -420
- package/src/social.yaml +246 -0
- package/src/templates.ts +12812 -12260
- package/src/the-arch.yaml +182 -0
- package/src/types.ts +105 -0
- package/dist/define.d.ts +0 -97
- package/dist/define.d.ts.map +0 -1
- package/dist/define.js +0 -91
- package/dist/index.d.ts.map +0 -1
- package/dist/templates.d.ts +0 -7
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js +0 -24237
- package/dist/types.d.ts +0 -350
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -8
- package/src/radar.yaml +0 -297
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synap-core/workspace-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "pnpm@9.15.4",
|
|
6
6
|
"description": "Canonical workspace definitions — YAML source + typed exports for browser, backend, CLI, and marketplace",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
|
-
"types": "./
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"node": "./dist/index.js",
|
|
11
|
-
"default": "./
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"type-check": "tsc --noEmit",
|
|
20
20
|
"build:yaml": "node build.mjs",
|
|
21
|
-
"
|
|
21
|
+
"check:converters": "node scripts/check-converters.mjs",
|
|
22
|
+
"build": "node build.mjs && tsup && node scripts/check-converters.mjs",
|
|
22
23
|
"provision": "node scripts/provision.mjs",
|
|
23
24
|
"eval": "node scripts/eval-standard.mjs",
|
|
24
|
-
"prepublishOnly": "node build.mjs &&
|
|
25
|
+
"prepublishOnly": "node build.mjs && tsup && node scripts/check-converters.mjs"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"zod": "^3.0.0",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@types/js-yaml": "^4.0.9",
|
|
32
33
|
"@types/node": "^22.0.0",
|
|
34
|
+
"tsup": "^8.0.0",
|
|
33
35
|
"typescript": "^5.5.0"
|
|
34
36
|
},
|
|
35
37
|
"publishConfig": {
|
package/src/agent-fleet.yaml
CHANGED
|
@@ -895,28 +895,36 @@ displayTemplates:
|
|
|
895
895
|
bento:
|
|
896
896
|
viewName: "Agent Fleet Home"
|
|
897
897
|
blocks:
|
|
898
|
-
-
|
|
898
|
+
- kind: widget
|
|
899
|
+
widgetType: welcome-header
|
|
899
900
|
pos: { x: 0, y: 0, w: 12, h: 2 }
|
|
900
901
|
config: { title: "Agent Fleet", subtitle: "Your sovereign control plane for the agent fleet." }
|
|
901
|
-
-
|
|
902
|
+
- kind: widget
|
|
903
|
+
widgetType: entity-count
|
|
902
904
|
pos: { x: 0, y: 2, w: 3, h: 2 }
|
|
903
905
|
config: { profileSlug: agent, label: Agents, icon: bot }
|
|
904
|
-
-
|
|
906
|
+
- kind: widget
|
|
907
|
+
widgetType: entity-count
|
|
905
908
|
pos: { x: 3, y: 2, w: 3, h: 2 }
|
|
906
909
|
config: { profileSlug: task, label: Tasks, icon: check-square }
|
|
907
|
-
-
|
|
910
|
+
- kind: widget
|
|
911
|
+
widgetType: entity-count
|
|
908
912
|
pos: { x: 6, y: 2, w: 3, h: 2 }
|
|
909
913
|
config: { profileSlug: skill, label: Skills, icon: zap }
|
|
910
|
-
-
|
|
914
|
+
- kind: widget
|
|
915
|
+
widgetType: entity-count
|
|
911
916
|
pos: { x: 9, y: 2, w: 3, h: 2 }
|
|
912
917
|
config: { profileSlug: provider, label: Providers, icon: database }
|
|
913
|
-
-
|
|
918
|
+
- kind: widget
|
|
919
|
+
widgetType: entity-spotlight
|
|
914
920
|
pos: { x: 0, y: 4, w: 4, h: 4 }
|
|
915
921
|
config: { profileSlug: agent, seed: random, layout: detail, subtitleField: status, color: "#1A3A5C" }
|
|
916
|
-
-
|
|
922
|
+
- kind: widget
|
|
923
|
+
widgetType: entity-list
|
|
917
924
|
pos: { x: 4, y: 4, w: 4, h: 4 }
|
|
918
925
|
config: { profileSlug: task, title: "Active Tasks", limit: 6, variant: compact }
|
|
919
|
-
-
|
|
926
|
+
- kind: widget
|
|
927
|
+
widgetType: entity-list
|
|
920
928
|
pos: { x: 8, y: 4, w: 4, h: 4 }
|
|
921
929
|
config: { profileSlug: skill, title: "Top Skills", limit: 6, variant: compact }
|
|
922
930
|
- kind: view
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Blockchain Ecosystem — PRIVATE OVERLAY on the Ecosystem workspace.
|
|
2
|
+
# AI-generation-friendly YAML. Validated against types.ts at build time.
|
|
3
|
+
#
|
|
4
|
+
# This is an OVERLAY, not a standalone workspace: it `compose`s onto the
|
|
5
|
+
# Ecosystem base and layers its web3 market actors (market-makers, auditors,
|
|
6
|
+
# dev-shops, KOLs, agencies), the actor↔actor relations, and a couple of curated
|
|
7
|
+
# views ADDITIVELY onto that base. No separate workspace is created.
|
|
8
|
+
#
|
|
9
|
+
# Overlay-safety: subtype MATCHES the base (`ecosystem`, a no-op on the additive
|
|
10
|
+
# reconcile), and workspace `visibility`/`capabilities` are OMITTED so the base
|
|
11
|
+
# Ecosystem keeps its own identity (the reconcile REPLACES those when set).
|
|
12
|
+
# The 6 web3 role-profiles are moved here verbatim from grants.yaml. company/person
|
|
13
|
+
# are NOT redeclared — the reconcile resolves base profiles the overlay references.
|
|
14
|
+
|
|
15
|
+
meta:
|
|
16
|
+
slug: blockchain-ecosystem
|
|
17
|
+
name: Blockchain Ecosystem
|
|
18
|
+
description: "Web3 market actors — market-makers, auditors, dev shops, KOLs — and how they connect. Overlays the Ecosystem workspace."
|
|
19
|
+
icon: boxes
|
|
20
|
+
color: "#8B5CF6"
|
|
21
|
+
tags: [blockchain, web3, ecosystem, market-makers]
|
|
22
|
+
isPublic: false
|
|
23
|
+
|
|
24
|
+
# OVERLAY on Ecosystem — composes additively onto the base workspace.
|
|
25
|
+
dependencies:
|
|
26
|
+
- slug: ecosystem
|
|
27
|
+
kind: workspace
|
|
28
|
+
relation: compose
|
|
29
|
+
reason: "Adds web3 market actors + relations onto your Ecosystem."
|
|
30
|
+
|
|
31
|
+
workspace:
|
|
32
|
+
name: Ecosystem
|
|
33
|
+
description: "Web3 market actors and the relationships between them, layered onto your Ecosystem."
|
|
34
|
+
subtype: ecosystem
|
|
35
|
+
|
|
36
|
+
profiles:
|
|
37
|
+
# ── Web3 actor roles (facets on company/person) ─────────────────────────────
|
|
38
|
+
# Moved here VERBATIM from grants.yaml — web3-specific service roles an actor
|
|
39
|
+
# plays. `scope: shared` dedups to the ONE pod-wide role profile.
|
|
40
|
+
|
|
41
|
+
# ── Market Maker (liquidity provider for a token) ─────────────────────────
|
|
42
|
+
- slug: market-maker
|
|
43
|
+
displayName: Market Maker
|
|
44
|
+
icon: candlestick-chart
|
|
45
|
+
color: sky
|
|
46
|
+
description: A liquidity provider — makes markets and provides depth for a token.
|
|
47
|
+
scope: shared
|
|
48
|
+
profileKind: role
|
|
49
|
+
applicableKinds:
|
|
50
|
+
- company
|
|
51
|
+
- person
|
|
52
|
+
properties:
|
|
53
|
+
- slug: status
|
|
54
|
+
label: Status
|
|
55
|
+
valueType: string
|
|
56
|
+
inputType: select
|
|
57
|
+
enumValues:
|
|
58
|
+
- prospect
|
|
59
|
+
- active
|
|
60
|
+
- paused
|
|
61
|
+
- churned
|
|
62
|
+
- slug: exchanges
|
|
63
|
+
label: Exchanges / Venues
|
|
64
|
+
valueType: array
|
|
65
|
+
inputType: text
|
|
66
|
+
- slug: engagementStartDate
|
|
67
|
+
label: Engagement Start
|
|
68
|
+
valueType: date
|
|
69
|
+
inputType: date
|
|
70
|
+
- slug: notes
|
|
71
|
+
label: Notes
|
|
72
|
+
valueType: string
|
|
73
|
+
inputType: textarea
|
|
74
|
+
|
|
75
|
+
# ── Security Auditor (smart-contract / protocol audits) ───────────────────
|
|
76
|
+
- slug: security-auditor
|
|
77
|
+
displayName: Security Auditor
|
|
78
|
+
icon: shield-check
|
|
79
|
+
color: red
|
|
80
|
+
description: A security firm or individual that audits smart contracts and protocols.
|
|
81
|
+
scope: shared
|
|
82
|
+
profileKind: role
|
|
83
|
+
applicableKinds:
|
|
84
|
+
- company
|
|
85
|
+
- person
|
|
86
|
+
properties:
|
|
87
|
+
- slug: status
|
|
88
|
+
label: Status
|
|
89
|
+
valueType: string
|
|
90
|
+
inputType: select
|
|
91
|
+
enumValues:
|
|
92
|
+
- prospect
|
|
93
|
+
- active
|
|
94
|
+
- paused
|
|
95
|
+
- churned
|
|
96
|
+
- slug: specialty
|
|
97
|
+
label: Specialty
|
|
98
|
+
valueType: string
|
|
99
|
+
inputType: text
|
|
100
|
+
- slug: engagementStartDate
|
|
101
|
+
label: Engagement Start
|
|
102
|
+
valueType: date
|
|
103
|
+
inputType: date
|
|
104
|
+
- slug: notes
|
|
105
|
+
label: Notes
|
|
106
|
+
valueType: string
|
|
107
|
+
inputType: textarea
|
|
108
|
+
|
|
109
|
+
# ── Dev Shop (outsourced engineering / build partner) ─────────────────────
|
|
110
|
+
- slug: dev-shop
|
|
111
|
+
displayName: Dev Shop
|
|
112
|
+
icon: code
|
|
113
|
+
color: violet
|
|
114
|
+
description: An outsourced engineering studio or build partner.
|
|
115
|
+
scope: shared
|
|
116
|
+
profileKind: role
|
|
117
|
+
applicableKinds:
|
|
118
|
+
- company
|
|
119
|
+
- person
|
|
120
|
+
properties:
|
|
121
|
+
- slug: status
|
|
122
|
+
label: Status
|
|
123
|
+
valueType: string
|
|
124
|
+
inputType: select
|
|
125
|
+
enumValues:
|
|
126
|
+
- prospect
|
|
127
|
+
- active
|
|
128
|
+
- paused
|
|
129
|
+
- churned
|
|
130
|
+
- slug: specialty
|
|
131
|
+
label: Specialty
|
|
132
|
+
valueType: string
|
|
133
|
+
inputType: text
|
|
134
|
+
- slug: engagementStartDate
|
|
135
|
+
label: Engagement Start
|
|
136
|
+
valueType: date
|
|
137
|
+
inputType: date
|
|
138
|
+
- slug: notes
|
|
139
|
+
label: Notes
|
|
140
|
+
valueType: string
|
|
141
|
+
inputType: textarea
|
|
142
|
+
|
|
143
|
+
# ── Marketing Agency (brand / growth / content marketing) ─────────────────
|
|
144
|
+
- slug: marketing-agency
|
|
145
|
+
displayName: Marketing Agency
|
|
146
|
+
icon: megaphone
|
|
147
|
+
color: pink
|
|
148
|
+
description: A brand, growth, or content marketing agency.
|
|
149
|
+
scope: shared
|
|
150
|
+
profileKind: role
|
|
151
|
+
applicableKinds:
|
|
152
|
+
- company
|
|
153
|
+
- person
|
|
154
|
+
properties:
|
|
155
|
+
- slug: status
|
|
156
|
+
label: Status
|
|
157
|
+
valueType: string
|
|
158
|
+
inputType: select
|
|
159
|
+
enumValues:
|
|
160
|
+
- prospect
|
|
161
|
+
- active
|
|
162
|
+
- paused
|
|
163
|
+
- churned
|
|
164
|
+
- slug: specialty
|
|
165
|
+
label: Specialty
|
|
166
|
+
valueType: string
|
|
167
|
+
inputType: text
|
|
168
|
+
- slug: engagementStartDate
|
|
169
|
+
label: Engagement Start
|
|
170
|
+
valueType: date
|
|
171
|
+
inputType: date
|
|
172
|
+
- slug: notes
|
|
173
|
+
label: Notes
|
|
174
|
+
valueType: string
|
|
175
|
+
inputType: textarea
|
|
176
|
+
|
|
177
|
+
# ── Paid-Ads Agency (performance / paid media buying) ─────────────────────
|
|
178
|
+
- slug: paid-ads-agency
|
|
179
|
+
displayName: Paid-Ads Agency
|
|
180
|
+
icon: badge-dollar-sign
|
|
181
|
+
color: orange
|
|
182
|
+
description: A performance / paid-media buying agency.
|
|
183
|
+
scope: shared
|
|
184
|
+
profileKind: role
|
|
185
|
+
applicableKinds:
|
|
186
|
+
- company
|
|
187
|
+
- person
|
|
188
|
+
properties:
|
|
189
|
+
- slug: status
|
|
190
|
+
label: Status
|
|
191
|
+
valueType: string
|
|
192
|
+
inputType: select
|
|
193
|
+
enumValues:
|
|
194
|
+
- prospect
|
|
195
|
+
- active
|
|
196
|
+
- paused
|
|
197
|
+
- churned
|
|
198
|
+
- slug: channels
|
|
199
|
+
label: Channels
|
|
200
|
+
valueType: array
|
|
201
|
+
inputType: text
|
|
202
|
+
- slug: engagementStartDate
|
|
203
|
+
label: Engagement Start
|
|
204
|
+
valueType: date
|
|
205
|
+
inputType: date
|
|
206
|
+
- slug: notes
|
|
207
|
+
label: Notes
|
|
208
|
+
valueType: string
|
|
209
|
+
inputType: textarea
|
|
210
|
+
|
|
211
|
+
# ── KOL (key opinion leader / influencer) ─────────────────────────────────
|
|
212
|
+
- slug: kol
|
|
213
|
+
displayName: KOL
|
|
214
|
+
icon: star
|
|
215
|
+
color: yellow
|
|
216
|
+
description: A key opinion leader or influencer who amplifies reach.
|
|
217
|
+
scope: shared
|
|
218
|
+
profileKind: role
|
|
219
|
+
applicableKinds:
|
|
220
|
+
- company
|
|
221
|
+
- person
|
|
222
|
+
properties:
|
|
223
|
+
- slug: status
|
|
224
|
+
label: Status
|
|
225
|
+
valueType: string
|
|
226
|
+
inputType: select
|
|
227
|
+
enumValues:
|
|
228
|
+
- prospect
|
|
229
|
+
- active
|
|
230
|
+
- paused
|
|
231
|
+
- churned
|
|
232
|
+
- slug: platform
|
|
233
|
+
label: Primary Platform
|
|
234
|
+
valueType: string
|
|
235
|
+
inputType: text
|
|
236
|
+
- slug: audienceSize
|
|
237
|
+
label: Audience Size
|
|
238
|
+
valueType: number
|
|
239
|
+
inputType: number
|
|
240
|
+
- slug: notes
|
|
241
|
+
label: Notes
|
|
242
|
+
valueType: string
|
|
243
|
+
inputType: textarea
|
|
244
|
+
|
|
245
|
+
# Web3 actor↔actor relations (company→company). `company` resolves from the
|
|
246
|
+
# Ecosystem base — not redeclared here.
|
|
247
|
+
entityLinks:
|
|
248
|
+
- sourceProfileSlug: company
|
|
249
|
+
targetProfileSlug: company
|
|
250
|
+
type: audited_by
|
|
251
|
+
label: Audited by
|
|
252
|
+
- sourceProfileSlug: company
|
|
253
|
+
targetProfileSlug: company
|
|
254
|
+
type: makes_market_for
|
|
255
|
+
label: Makes market for
|
|
256
|
+
- sourceProfileSlug: company
|
|
257
|
+
targetProfileSlug: company
|
|
258
|
+
type: built_by
|
|
259
|
+
label: Built by
|
|
260
|
+
- sourceProfileSlug: company
|
|
261
|
+
targetProfileSlug: company
|
|
262
|
+
type: advises
|
|
263
|
+
label: Advises
|
|
264
|
+
|
|
265
|
+
# Curated views over the web3 roles (companies wearing each role).
|
|
266
|
+
views:
|
|
267
|
+
- name: Market Makers
|
|
268
|
+
slug: market-makers
|
|
269
|
+
type: table
|
|
270
|
+
scopeProfileSlug: market-maker
|
|
271
|
+
description: Liquidity providers you work with, by status.
|
|
272
|
+
config:
|
|
273
|
+
columns: [title, status, exchanges, engagementStartDate]
|
|
274
|
+
- name: Auditors
|
|
275
|
+
slug: auditors
|
|
276
|
+
type: table
|
|
277
|
+
scopeProfileSlug: security-auditor
|
|
278
|
+
description: Security auditors you work with, by status.
|
|
279
|
+
config:
|
|
280
|
+
columns: [title, status, specialty, engagementStartDate]
|
|
281
|
+
- name: Dev Shops
|
|
282
|
+
slug: dev-shops
|
|
283
|
+
type: table
|
|
284
|
+
scopeProfileSlug: dev-shop
|
|
285
|
+
description: Build partners and engineering studios you work with.
|
|
286
|
+
config:
|
|
287
|
+
columns: [title, status, specialty, engagementStartDate]
|
|
288
|
+
|
|
289
|
+
onboarding:
|
|
290
|
+
goal: >
|
|
291
|
+
Map the web3 service ecosystem — which market-makers, auditors, dev-shops,
|
|
292
|
+
and KOLs you work with, and how they connect to the projects and companies in
|
|
293
|
+
your Ecosystem — so the map shows not just the players but the service graph
|
|
294
|
+
around them.
|
|
295
|
+
framing: |
|
|
296
|
+
THE WEB3 ECOSYSTEM LEAD: Act as a sharp web3 ecosystem lead who knows the
|
|
297
|
+
service layer of the market — who provides liquidity, who audits, who ships
|
|
298
|
+
code, who moves attention. Push for REAL named firms and the concrete edges
|
|
299
|
+
between them: who audits whom, who makes markets for which token, who built
|
|
300
|
+
which product. Tag the role onto the real company, then draw the edge — a list
|
|
301
|
+
of service providers with no relationships is just a directory.
|
|
302
|
+
expertise:
|
|
303
|
+
starters:
|
|
304
|
+
- >
|
|
305
|
+
Tag the role onto the real company, don't spin up a new one. A market-maker
|
|
306
|
+
or auditor is a HAT a company wears — attach the role to the existing
|
|
307
|
+
Ecosystem company (match on domain / name first) rather than creating a
|
|
308
|
+
second entity for the same firm.
|
|
309
|
+
- >
|
|
310
|
+
The service layer is a graph, not a list. For every provider you log,
|
|
311
|
+
propose at least one real edge — audited_by, makes_market_for, built_by,
|
|
312
|
+
advises — because the value is in who serves whom, not in the roster alone.
|
|
313
|
+
blindSpots:
|
|
314
|
+
- >
|
|
315
|
+
Founders track the loud KOLs and forget the load-bearing providers — the
|
|
316
|
+
auditor gating a launch, the market-maker holding the book. Surface the
|
|
317
|
+
quiet, critical relationships, not just the visible ones.
|
|
318
|
+
- >
|
|
319
|
+
A service provider with no engagement status and no edge is a bookmark, not
|
|
320
|
+
a relationship. Force the status (prospect/active/paused/churned) and at
|
|
321
|
+
least one connection to a real project or company.
|
|
322
|
+
bar: >
|
|
323
|
+
The web3 service graph reads true: named firms wearing the right role
|
|
324
|
+
(market-maker, auditor, dev-shop, KOL), each with an engagement status and at
|
|
325
|
+
least one real edge to the company or project it serves — so a glance shows
|
|
326
|
+
who's providing liquidity, who's auditing, who's building, and who's amplifying.
|
|
327
|
+
collect:
|
|
328
|
+
- profileSlug: market-maker
|
|
329
|
+
what: The market-makers / liquidity providers you work with, tagged onto the real company.
|
|
330
|
+
cardinality: few
|
|
331
|
+
keyFields: [status, exchanges]
|
|
332
|
+
- profileSlug: security-auditor
|
|
333
|
+
what: The security auditors you work with, tagged onto the real firm.
|
|
334
|
+
cardinality: few
|
|
335
|
+
keyFields: [status, specialty]
|
|
336
|
+
openingQuestions:
|
|
337
|
+
- "Which market-makers or liquidity providers are in your orbit, and are they active?"
|
|
338
|
+
- "Who audits the projects you work with — which security firms do you trust?"
|
|
339
|
+
- "Which dev-shops or KOLs are part of your ecosystem, and how do they connect to your projects?"
|
|
340
|
+
doneWhen: a couple of web3 actors tagged onto real companies with an engagement status.
|
|
341
|
+
|
|
342
|
+
app:
|
|
343
|
+
kind: stored
|
|
344
|
+
cellKey: entity-detail
|
|
345
|
+
|
|
346
|
+
layout:
|
|
347
|
+
defaultView: Market Makers
|
|
348
|
+
defaultApp: null
|
|
349
|
+
pinnedApps: [dashboard, data]
|
|
350
|
+
sidebarItems:
|
|
351
|
+
- kind: view
|
|
352
|
+
viewName: Market Makers
|
|
353
|
+
label: Market Makers
|
|
354
|
+
icon: candlestick-chart
|
|
355
|
+
- kind: view
|
|
356
|
+
viewName: Auditors
|
|
357
|
+
label: Auditors
|
|
358
|
+
icon: shield-check
|
|
359
|
+
- kind: view
|
|
360
|
+
viewName: Dev Shops
|
|
361
|
+
label: Dev Shops
|
|
362
|
+
icon: code
|
|
363
|
+
|
|
364
|
+
bento:
|
|
365
|
+
viewName: Market Makers
|
|
366
|
+
blocks:
|
|
367
|
+
- kind: view
|
|
368
|
+
viewName: Market Makers
|
|
369
|
+
pos: { x: 0, y: 0, w: 6, h: 5 }
|
|
370
|
+
- kind: view
|
|
371
|
+
viewName: Auditors
|
|
372
|
+
pos: { x: 6, y: 0, w: 6, h: 5 }
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the mergeCatalog door. Run: `npx tsx --test src/catalog.test.ts`
|
|
3
|
+
*
|
|
4
|
+
* These test the SOURCE (`./catalog.js` → catalog.ts), never `dist/`, so no build
|
|
5
|
+
* is required and there is no stale-generated-output trap.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mergeCatalog, type RemoteEntry } from "./catalog.js";
|
|
11
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const BUNDLE_VERSION = "0.6.0";
|
|
14
|
+
|
|
15
|
+
/** Minimal bundled template — only the fields the door reads. */
|
|
16
|
+
function bundled(slug: string, extra?: Partial<WorkspaceYaml["meta"]>): WorkspaceYaml {
|
|
17
|
+
return {
|
|
18
|
+
meta: { slug, name: slug, description: `${slug} desc`, icon: "layers", color: "#fff", ...extra },
|
|
19
|
+
workspace: { name: slug, description: "" },
|
|
20
|
+
profiles: [],
|
|
21
|
+
} as WorkspaceYaml;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Minimal CP row. `version` (when given) lands at definition.sourcePackage.version. */
|
|
25
|
+
function remote(slug: string, opts?: { version?: string; isPrivate?: boolean }): RemoteEntry {
|
|
26
|
+
return {
|
|
27
|
+
slug,
|
|
28
|
+
name: slug,
|
|
29
|
+
description: `${slug} remote`,
|
|
30
|
+
category: "workspace",
|
|
31
|
+
tags: [],
|
|
32
|
+
isPrivate: opts?.isPrivate,
|
|
33
|
+
definition:
|
|
34
|
+
opts?.version !== undefined
|
|
35
|
+
? { sourcePackage: { name: "@synap-core/workspace-templates", version: opts.version } }
|
|
36
|
+
: undefined,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Dedupe by slug ──────────────────────────────────────────────────────────
|
|
41
|
+
// MUTATION PROOF: change the dedupe key to `slug@version` and this count goes to
|
|
42
|
+
// 2 (bundled "1.0.0"-implied vs remote) → RED.
|
|
43
|
+
test("dedupes a slug present in both sources into ONE entry", () => {
|
|
44
|
+
const { entries } = mergeCatalog({
|
|
45
|
+
bundled: [bundled("crm"), bundled("legal")],
|
|
46
|
+
remote: [remote("crm", { version: "0.5.0" }), remote("legal", { version: "0.5.0" })],
|
|
47
|
+
bundleVersion: BUNDLE_VERSION,
|
|
48
|
+
});
|
|
49
|
+
assert.equal(entries.length, 2, "two slugs, deduped");
|
|
50
|
+
assert.equal(entries.filter((e) => e.slug === "crm").length, 1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// ── Winner rule ─────────────────────────────────────────────────────────────
|
|
54
|
+
test("bundle wins when remote version EQUALS the bundle", () => {
|
|
55
|
+
const { entries } = mergeCatalog({
|
|
56
|
+
bundled: [bundled("crm")],
|
|
57
|
+
remote: [remote("crm", { version: BUNDLE_VERSION })],
|
|
58
|
+
bundleVersion: BUNDLE_VERSION,
|
|
59
|
+
});
|
|
60
|
+
assert.equal(entries[0].source, "bundled");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("bundle wins when remote version is OLDER", () => {
|
|
64
|
+
const { entries } = mergeCatalog({
|
|
65
|
+
bundled: [bundled("crm")],
|
|
66
|
+
remote: [remote("crm", { version: "0.5.0" })],
|
|
67
|
+
bundleVersion: BUNDLE_VERSION,
|
|
68
|
+
});
|
|
69
|
+
assert.equal(entries[0].source, "bundled");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// MUTATION PROOF: flip `remoteIsStrictlyNewer`'s comparison (`>` → `<`, or return
|
|
73
|
+
// false) and this assertion (source === "remote") goes RED.
|
|
74
|
+
test("remote wins when it is STRICTLY newer (this client is stale)", () => {
|
|
75
|
+
const { entries } = mergeCatalog({
|
|
76
|
+
bundled: [bundled("crm")],
|
|
77
|
+
remote: [remote("crm", { version: "0.7.0" })],
|
|
78
|
+
bundleVersion: BUNDLE_VERSION,
|
|
79
|
+
});
|
|
80
|
+
assert.equal(entries[0].source, "remote");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ── Third-party & squatting ─────────────────────────────────────────────────
|
|
84
|
+
test("a non-colliding remote slug is kept as a DISTINCT entry", () => {
|
|
85
|
+
const { entries } = mergeCatalog({
|
|
86
|
+
bundled: [bundled("crm")],
|
|
87
|
+
remote: [remote("acme-widget")], // no sourcePackage, no bundled twin
|
|
88
|
+
bundleVersion: BUNDLE_VERSION,
|
|
89
|
+
});
|
|
90
|
+
assert.equal(entries.length, 2);
|
|
91
|
+
const acme = entries.find((e) => e.slug === "acme-widget");
|
|
92
|
+
assert.equal(acme?.source, "remote");
|
|
93
|
+
assert.equal(acme?.isPrivate, false, "browse rows default public");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("a third-party row SQUATTING an official slug (no sourcePackage) loses to the bundle", () => {
|
|
97
|
+
const { entries } = mergeCatalog({
|
|
98
|
+
bundled: [bundled("crm")],
|
|
99
|
+
remote: [remote("crm")], // slug collision, NO sourcePackage → squatting
|
|
100
|
+
bundleVersion: BUNDLE_VERSION,
|
|
101
|
+
});
|
|
102
|
+
assert.equal(entries.length, 1);
|
|
103
|
+
assert.equal(entries[0].source, "bundled");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ── sources degradation (load-bearing) ──────────────────────────────────────
|
|
107
|
+
test("remote unreachable → returns bundled only + honest source status", () => {
|
|
108
|
+
const { entries, sources } = mergeCatalog({
|
|
109
|
+
bundled: [bundled("crm"), bundled("legal")],
|
|
110
|
+
remote: [], // fetch failed → empty
|
|
111
|
+
bundleVersion: BUNDLE_VERSION,
|
|
112
|
+
remoteStatus: "unreachable",
|
|
113
|
+
});
|
|
114
|
+
assert.equal(entries.length, 2, "still shows all bundled");
|
|
115
|
+
assert.equal(sources.bundled, "ok");
|
|
116
|
+
assert.equal(sources.remote, "unreachable");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("remote unauthenticated → status surfaced; any stray remote rows are ignored", () => {
|
|
120
|
+
const { entries, sources } = mergeCatalog({
|
|
121
|
+
bundled: [bundled("crm")],
|
|
122
|
+
remote: [remote("private-pack", { isPrivate: true })], // must NOT be merged
|
|
123
|
+
bundleVersion: BUNDLE_VERSION,
|
|
124
|
+
remoteStatus: "unauthenticated",
|
|
125
|
+
});
|
|
126
|
+
assert.equal(entries.length, 1);
|
|
127
|
+
assert.equal(entries[0].slug, "crm");
|
|
128
|
+
assert.equal(sources.remote, "unauthenticated");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("private remote packages merge when reachable and carry isPrivate", () => {
|
|
132
|
+
const { entries } = mergeCatalog({
|
|
133
|
+
bundled: [bundled("crm")],
|
|
134
|
+
remote: [remote("private-pack", { isPrivate: true })],
|
|
135
|
+
bundleVersion: BUNDLE_VERSION,
|
|
136
|
+
remoteStatus: "ok",
|
|
137
|
+
});
|
|
138
|
+
const priv = entries.find((e) => e.slug === "private-pack");
|
|
139
|
+
assert.equal(priv?.source, "remote");
|
|
140
|
+
assert.equal(priv?.isPrivate, true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// ── Real bundled data shape (integration-ish) ───────────────────────────────
|
|
144
|
+
test("maps a bundled template's meta fields onto the entry", () => {
|
|
145
|
+
const { entries } = mergeCatalog({
|
|
146
|
+
bundled: [bundled("crm", { icon: "handshake", tags: ["crm", "sales"], isPublic: false })],
|
|
147
|
+
remote: [],
|
|
148
|
+
bundleVersion: BUNDLE_VERSION,
|
|
149
|
+
});
|
|
150
|
+
const e = entries[0];
|
|
151
|
+
assert.equal(e.source, "bundled");
|
|
152
|
+
assert.equal(e.icon, "handshake");
|
|
153
|
+
assert.deepEqual(e.tags, ["crm", "sales"]);
|
|
154
|
+
assert.equal(e.category, "workspace");
|
|
155
|
+
assert.equal(e.isPrivate, true, "isPublic:false ⇒ isPrivate");
|
|
156
|
+
});
|