archsight 0.3.3 → 0.4.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.
Files changed (193) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -1
  3. data/docs/archimate.md +21 -1
  4. data/docs/configuration.md +1 -0
  5. data/docs/diagram.md +15 -3
  6. data/docs/docker.md +4 -1
  7. data/docs/icons.md +8 -2
  8. data/docs/import.md +178 -55
  9. data/docs/index.md.erb +24 -18
  10. data/docs/modeling.md +645 -34
  11. data/docs/pages.md +46 -15
  12. data/docs/search.md +21 -1
  13. data/docs/togaf.md +2 -1
  14. data/lib/archsight/analysis/result.rb +8 -2
  15. data/lib/archsight/analysis/sandbox.rb +8 -0
  16. data/lib/archsight/analysis/typst_renderer.rb +245 -0
  17. data/lib/archsight/analysis.rb +1 -0
  18. data/lib/archsight/annotations/annotation.rb +30 -3
  19. data/lib/archsight/annotations/architecture_annotations.rb +47 -0
  20. data/lib/archsight/annotations/link_annotations.rb +15 -0
  21. data/lib/archsight/annotations/resource_reference.rb +12 -0
  22. data/lib/archsight/cli.rb +124 -23
  23. data/lib/archsight/database.rb +40 -0
  24. data/lib/archsight/diagram/cli.rb +50 -2
  25. data/lib/archsight/diagram/formatter.rb +112 -0
  26. data/lib/archsight/diagram/routing/edge_router/orthogonal_path.rb +38 -10
  27. data/lib/archsight/diagram/routing/edge_router.rb +29 -4
  28. data/lib/archsight/diagram/routing/edge_routing.rb +61 -5
  29. data/lib/archsight/diagram/style/theme.rb +7 -2
  30. data/lib/archsight/documentation.rb +4 -4
  31. data/lib/archsight/editor/page_source.rb +13 -1
  32. data/lib/archsight/export/confluence/analysis_output.rb +100 -0
  33. data/lib/archsight/export/confluence/client.rb +13 -0
  34. data/lib/archsight/export/confluence/diagram_links.rb +1 -1
  35. data/lib/archsight/export/confluence/exporter.rb +43 -14
  36. data/lib/archsight/export/confluence/labels.rb +25 -0
  37. data/lib/archsight/export/confluence/page_properties.rb +6 -9
  38. data/lib/archsight/export/confluence/storage.rb +63 -14
  39. data/lib/archsight/export/confluence/tables.rb +2 -3
  40. data/lib/archsight/git_source.rb +81 -0
  41. data/lib/archsight/helpers/asset_images.rb +3 -4
  42. data/lib/archsight/helpers/embeds.rb +18 -7
  43. data/lib/archsight/helpers/resource_resolver.rb +25 -0
  44. data/lib/archsight/import/executor.rb +140 -16
  45. data/lib/archsight/import/handler.rb +55 -6
  46. data/lib/archsight/import/handler_loader.rb +64 -0
  47. data/lib/archsight/import/handlers/cache_pruner.rb +30 -7
  48. data/lib/archsight/import/handlers/command.rb +120 -0
  49. data/lib/archsight/import/handlers/github.rb +4 -1
  50. data/lib/archsight/import/handlers/gitlab.rb +4 -1
  51. data/lib/archsight/import/handlers/go_dep_resolver.rb +10 -2
  52. data/lib/archsight/import/handlers/go_grapher.rb +102 -5
  53. data/lib/archsight/import/handlers/go_module_parser.rb +88 -0
  54. data/lib/archsight/import/handlers/grapher.rb +40 -1
  55. data/lib/archsight/import/handlers/repository.rb +3 -0
  56. data/lib/archsight/import/handlers/rest_api_index.rb +3 -0
  57. data/lib/archsight/import/registry.rb +22 -1
  58. data/lib/archsight/import.rb +89 -1
  59. data/lib/archsight/linter/structure.rb +239 -0
  60. data/lib/archsight/linter.rb +123 -16
  61. data/lib/archsight/markdown.rb +48 -0
  62. data/lib/archsight/openapi.rb +169 -0
  63. data/lib/archsight/page_loader.rb +12 -1
  64. data/lib/archsight/page_tree.rb +2 -3
  65. data/lib/archsight/query/evaluator.rb +13 -6
  66. data/lib/archsight/query/parser.rb +73 -6
  67. data/lib/archsight/query.rb +1 -1
  68. data/lib/archsight/references.rb +3 -3
  69. data/lib/archsight/renderer.rb +80 -23
  70. data/lib/archsight/resources/analysis.rb +3 -2
  71. data/lib/archsight/resources/application_component.rb +75 -7
  72. data/lib/archsight/resources/application_interface.rb +12 -0
  73. data/lib/archsight/resources/application_service.rb +12 -1
  74. data/lib/archsight/resources/base.rb +58 -5
  75. data/lib/archsight/resources/business_actor.rb +5 -0
  76. data/lib/archsight/resources/business_contract.rb +72 -0
  77. data/lib/archsight/resources/business_product.rb +12 -0
  78. data/lib/archsight/resources/business_role.rb +1 -0
  79. data/lib/archsight/resources/implementation_deliverable.rb +1 -0
  80. data/lib/archsight/resources/implementation_plateau.rb +8 -2
  81. data/lib/archsight/resources/implementation_work_package.rb +2 -1
  82. data/lib/archsight/resources/import.rb +37 -11
  83. data/lib/archsight/resources/location.rb +56 -0
  84. data/lib/archsight/resources/motivation_requirement.rb +6 -0
  85. data/lib/archsight/resources/motivation_stakeholder.rb +14 -0
  86. data/lib/archsight/resources/page.rb +4 -7
  87. data/lib/archsight/resources/strategy_capability.rb +9 -0
  88. data/lib/archsight/resources/technology_artifact.rb +11 -4
  89. data/lib/archsight/resources/technology_communication_network.rb +91 -0
  90. data/lib/archsight/resources/technology_device.rb +88 -0
  91. data/lib/archsight/resources/technology_equipment.rb +75 -0
  92. data/lib/archsight/resources/technology_facility.rb +70 -0
  93. data/lib/archsight/resources/technology_node.rb +50 -0
  94. data/lib/archsight/resources/technology_service.rb +33 -0
  95. data/lib/archsight/resources/technology_system_software.rb +28 -0
  96. data/lib/archsight/resources.rb +12 -0
  97. data/lib/archsight/version.rb +1 -1
  98. data/lib/archsight/web/api/analysis_helpers.rb +55 -0
  99. data/lib/archsight/web/api/docs.rb +2 -1
  100. data/lib/archsight/web/api/json_helpers.rb +3 -37
  101. data/lib/archsight/web/api/openapi/spec.yaml +146 -35
  102. data/lib/archsight/web/api/page_helpers.rb +1 -8
  103. data/lib/archsight/web/api/routes.rb +9 -4
  104. data/lib/archsight/web/application.rb +40 -4
  105. data/lib/archsight/web/editor/routes.rb +2 -1
  106. data/lib/archsight/web/public/vendor/redoc/LICENSE +22 -0
  107. data/lib/archsight/web/public/vendor/redoc/VERSION +1 -0
  108. data/lib/archsight/web/public/vendor/redoc/api.html +27 -0
  109. data/lib/archsight/web/public/vendor/redoc/api.js +19 -0
  110. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js +1851 -0
  111. data/lib/archsight/web/public/vendor/redoc/redoc.standalone.js.LICENSE.txt +102 -0
  112. data/lib/archsight/web/public/vue/DocPage-C2ubTXH7.js +1 -0
  113. data/lib/archsight/web/public/vue/EditorPage-Dso6PLMn.js +35 -0
  114. data/lib/archsight/web/public/vue/ErrorPage-DnZ6YJLi.js +2 -0
  115. data/lib/archsight/web/public/vue/GraphView-CTl03Yre.js +1 -0
  116. data/lib/archsight/web/public/vue/HomePage-C339nihQ.js +2 -0
  117. data/lib/archsight/web/public/vue/InstanceRouter-DFW7tOrD.js +1 -0
  118. data/lib/archsight/web/public/vue/InstanceRouter-ZaCcjlIX.css +1 -0
  119. data/lib/archsight/web/public/vue/KindList-C0oaM3CF.js +1 -0
  120. data/lib/archsight/web/public/vue/KindList-yYcRT2BV.css +1 -0
  121. data/lib/archsight/web/public/vue/PageView-B93woeN3.css +1 -0
  122. data/lib/archsight/web/public/vue/PageView-OZB6G9Wu.js +1 -0
  123. data/lib/archsight/web/public/vue/QueryError-C2xLkCEm.js +1 -0
  124. data/lib/archsight/web/public/vue/ResourceList-D75O87zG.css +1 -0
  125. data/lib/archsight/web/public/vue/ResourceList-DooWFwQP.js +2 -0
  126. data/lib/archsight/web/public/vue/SearchResults-BK-HjFnv.js +1 -0
  127. data/lib/archsight/web/public/vue/{SearchResults-BewvsfOc.css → SearchResults-BV6UHriJ.css} +1 -1
  128. data/lib/archsight/web/public/vue/WikiPage-B2FYmeHL.js +3 -0
  129. data/lib/archsight/web/public/vue/WikiPage-TpmM1VAF.css +1 -0
  130. data/lib/archsight/web/public/vue/architecture-WOLXFQ4H-BGN-TV5r.js +1 -0
  131. data/lib/archsight/web/public/vue/cynefin-EF2NZ3EQ-CZd6g_tq.js +1 -0
  132. data/lib/archsight/web/public/vue/eventmodeling-K75KTNOO-CYnbEx8O.js +1 -0
  133. data/lib/archsight/web/public/vue/gitGraph-VSP46ZUC-DialfQSk.js +1 -0
  134. data/lib/archsight/web/public/vue/graphviz-1lWBhpEX.js +3 -0
  135. data/lib/archsight/web/public/vue/index-BLSE5DSv.css +1 -0
  136. data/lib/archsight/web/public/vue/index-CLuLTE5I.js +3 -0
  137. data/lib/archsight/web/public/vue/info-OHQRW6UA-BuhOW7ss.js +1 -0
  138. data/lib/archsight/web/public/vue/katex-B_JcIwqj.js +257 -0
  139. data/lib/archsight/web/public/vue/mermaid-C3bOamfn.js +3999 -0
  140. data/lib/archsight/web/public/vue/packet-JDAUHWVQ-CUeRz7w4.js +1 -0
  141. data/lib/archsight/web/public/vue/pie-XZMESJXO-DnuewJI6.js +1 -0
  142. data/lib/archsight/web/public/vue/radar-ABXABTNO-Bi9l8bcv.js +1 -0
  143. data/lib/archsight/web/public/vue/railroad-I3PHUGI6-DSlxFbxk.js +1 -0
  144. data/lib/archsight/web/public/vue/railroad-abnf-LNEFI6M7-D6Kqu8wu.js +1 -0
  145. data/lib/archsight/web/public/vue/railroad-ebnf-DWYD2UWJ-MR7uCYT0.js +1 -0
  146. data/lib/archsight/web/public/vue/railroad-peg-JR7BVNR7-BL7aKIc-.js +1 -0
  147. data/lib/archsight/web/public/vue/rolldown-runtime-COiEfhsA.js +1 -0
  148. data/lib/archsight/web/public/vue/treeView-D4JQ5SDB-CamtB04D.js +1 -0
  149. data/lib/archsight/web/public/vue/treemap-SAJKECNS-DeQxu_HM.js +1 -0
  150. data/lib/archsight/web/public/vue/{useGraphviz-DlSnFeCL.js → useGraphviz-Dnm2pkon.js} +2 -2
  151. data/lib/archsight/web/public/vue/useHighlight-BH32C7yt.js +10 -0
  152. data/lib/archsight/web/public/vue/usePanZoom-HxgEsqq2.js +12 -0
  153. data/lib/archsight/web/public/vue/wardley-7MLQ67FV-BvKIB6Tv.js +1 -0
  154. data/lib/archsight/web/public/vue.html +5 -5
  155. metadata +68 -41
  156. data/lib/archsight/web/public/vue/ApiDocsPage-BgqnQgwa.js +0 -1
  157. data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +0 -1
  158. data/lib/archsight/web/public/vue/DocPage-CNO71nBH.js +0 -1
  159. data/lib/archsight/web/public/vue/EditorPage-DR0FCNTz.js +0 -35
  160. data/lib/archsight/web/public/vue/ErrorPage-BfYArv6s.js +0 -2
  161. data/lib/archsight/web/public/vue/GraphView-BVTW30Ak.js +0 -1
  162. data/lib/archsight/web/public/vue/HomePage-Wk9P4Pma.js +0 -2
  163. data/lib/archsight/web/public/vue/InstanceRouter-BV8ycydz.js +0 -1
  164. data/lib/archsight/web/public/vue/InstanceRouter-jeavM6PW.css +0 -1
  165. data/lib/archsight/web/public/vue/KindList-C8yMsN5J.js +0 -1
  166. data/lib/archsight/web/public/vue/PageView-BaN6TyJB.js +0 -1
  167. data/lib/archsight/web/public/vue/QueryError-D790wH-a.js +0 -1
  168. data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +0 -1
  169. data/lib/archsight/web/public/vue/ResourceList-D-66nas2.js +0 -2
  170. data/lib/archsight/web/public/vue/SearchResults-CHyqIerQ.js +0 -1
  171. data/lib/archsight/web/public/vue/WikiPage-D2svpk6B.js +0 -13
  172. data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +0 -1
  173. data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +0 -1
  174. data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +0 -1
  175. data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +0 -1
  176. data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +0 -1
  177. data/lib/archsight/web/public/vue/graphviz-SXWTgZH5.js +0 -3
  178. data/lib/archsight/web/public/vue/index-BXXkUK1V.js +0 -3
  179. data/lib/archsight/web/public/vue/index-Cov1SnzY.css +0 -1
  180. data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +0 -1
  181. data/lib/archsight/web/public/vue/katex-CXMH3UgJ.js +0 -257
  182. data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +0 -3334
  183. data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +0 -1
  184. data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +0 -1
  185. data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +0 -1
  186. data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +0 -1
  187. data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +0 -1
  188. data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +0 -1
  189. data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +0 -1
  190. data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +0 -1
  191. data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +0 -1
  192. data/lib/archsight/web/public/vue/useHighlight-D5Gf8DLl.js +0 -10
  193. data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +0 -1
@@ -40,9 +40,11 @@ module Archsight
40
40
  def gnode(graph, inst, opts = {})
41
41
  label = "<TABLE><TR><TD><B>#{inst.name}</B></TD></TR><TR><TD>#{inst.klass}</TD></TR></TABLE>"
42
42
  label = inst.name if opts[:simple_label]
43
- graph.node gname(inst), class: css_class(inst), shape: :box,
44
- style: "rounded,filled", fontname: FONT, label: label,
45
- href: "/kinds/#{inst.klass}/instances/#{inst.name}"
43
+ attrs = { class: css_class(inst), shape: :box, style: "rounded,filled", fontname: FONT, label: label,
44
+ href: "/kinds/#{inst.klass}/instances/#{inst.name}" }
45
+ # A resource at the depth limit with more dependencies: dashed, its own page shows them
46
+ attrs.merge!(style: "rounded,filled,dashed", tooltip: "More dependencies: open it to explore them") if opts[:truncated]
47
+ graph.node gname(inst), attrs
46
48
  end
47
49
 
48
50
  def gedge(graph, a_inst, b_inst, label)
@@ -104,29 +106,84 @@ module Archsight
104
106
  end
105
107
  end
106
108
 
107
- def create_graph_one(db, klass_pat, name_pat)
108
- name = "#{klass_pat}:#{name_pat}"
109
- nodes = {}
110
- edges = {}
111
- Archsight::Graphvis.new(name).draw_dot do |g|
112
- klass = Archsight::Resources[klass_pat] || raise("kind #{klass_pat} unknown")
113
- instances = db.instances[klass]
114
- inst = instances[name_pat] || raise("name #{name_pat} for kind #{klass_pat} not found")
115
- create_graph_one_inst(db, g, inst, nodes, edges)
109
+ # The layer an instance graph also follows by default, below the instance's own layer (technology is the bottom
110
+ # of the stack; implementation points at what it changes)
111
+ NEXT_LAYER = { "motivation" => "strategy", "strategy" => "business", "business" => "application",
112
+ "application" => "technology", "implementation" => "application" }.freeze
113
+
114
+ # The layers the graph of a resource of this layer follows when its page opens: its own and the next one below,
115
+ # every layer for `other` (pages, views, locations, ... refer to anything)
116
+ def self.default_graph_layers(layer)
117
+ return Archsight::Resources::LAYERS.dup unless Archsight::Resources::LAYERS.include?(layer) && layer != "other"
118
+
119
+ [layer, NEXT_LAYER[layer]].compact
120
+ end
121
+
122
+ # The dependency graph of one instance: the resources its relations lead to, breadth first, so a resource gets
123
+ # the depth of its shortest path.
124
+ # @param max_depth [Integer, nil] follow relations this many steps from the instance (nil: all the way). A
125
+ # resource at the limit that has more relations is drawn dashed.
126
+ # @param layers [Array<String>, nil] follow only relations to resources of these layers (nil: every layer); the
127
+ # instance itself is always shown
128
+ def create_graph_one(db, klass_pat, name_pat, max_depth: nil, layers: nil)
129
+ klass = Archsight::Resources[klass_pat] || raise("kind #{klass_pat} unknown")
130
+ root = db.instances[klass]&.[](name_pat) || raise("name #{name_pat} for kind #{klass_pat} not found")
131
+
132
+ Archsight::Graphvis.new("#{klass_pat}:#{name_pat}").draw_dot do |g|
133
+ nodes = {}
134
+ edges = {}
135
+ queue = [[root, 0]]
136
+ while queue.any?
137
+ inst, depth = queue.shift
138
+ next if nodes[inst]
139
+
140
+ nodes[inst] = true
141
+ targets = graph_targets(inst, layers)
142
+ if max_depth && depth >= max_depth
143
+ gnode(g, inst, truncated: targets.any?)
144
+ next
145
+ end
146
+
147
+ gnode(g, inst)
148
+ targets.each do |verb, rel|
149
+ edge_name = "#{gname(inst)}|#{verb}|#{gname(rel)}"
150
+ gedge(g, inst, rel, verb) unless edges[edge_name]
151
+ edges[edge_name] = true
152
+ queue << [rel, depth + 1] unless nodes[rel]
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # The relations of a kind to its own kind (a team's sub-teams, a service's dependencies on other services) as
159
+ # [verb, key] pairs
160
+ def self.self_relations(klass)
161
+ kind = klass.name.to_s.split("::").last
162
+ klass.declared_relations.filter_map { |verb, key, target| [verb, key] if target.to_s == kind }
163
+ end
164
+
165
+ # The graph of a kind: its resources that are related to each other by its relations to its own kind (an org
166
+ # chart for BusinessActor). Resources without such a relation are left out, the kind's list shows them.
167
+ def create_graph_kind(db, klass_pat)
168
+ klass = Archsight::Resources[klass_pat] || raise("kind #{klass_pat} unknown")
169
+ relations = Archsight::GraphvisRenderer.self_relations(klass)
170
+ sources = (db.instances[klass] || {}).values.reject(&:abandoned?)
171
+ edges = sources.flat_map do |inst|
172
+ relations.flat_map { |verb, key| inst.relations(verb, key).reject(&:abandoned?).map { |rel| [inst, verb, rel] } }
173
+ end
174
+ edges.uniq! { |inst, verb, rel| "#{gname(inst)}|#{verb}|#{gname(rel)}" }
175
+
176
+ Archsight::Graphvis.new(klass_pat).draw_dot do |g|
177
+ edges.flat_map { |inst, _, rel| [inst, rel] }.uniq.each { |inst| gnode(g, inst, simple_label: true) }
178
+ edges.each { |inst, verb, rel| gedge(g, inst, rel, verb) }
116
179
  end
117
180
  end
118
181
 
119
- def create_graph_one_inst(db, graph, inst, nodes, edges)
120
- return if nodes[inst] # Already visited - prevent infinite recursion
121
-
122
- gnode(graph, inst)
123
- nodes[inst] = true
124
- inst.class.relations.each do |verb, kind|
125
- inst.relations(verb, kind).each do |rel|
126
- edge_name = "#{inst}|#{verb}|#{rel}"
127
- gedge(graph, inst, rel, verb) unless edges[edge_name]
128
- edges[edge_name] = true
129
- create_graph_one_inst(db, graph, rel, nodes, edges)
182
+ # The [verb, target] pairs of an instance's relations the graph follows: those to resources of the layers
183
+ def graph_targets(inst, layers)
184
+ inst.class.relations.flat_map do |verb, kind|
185
+ inst.relations(verb, kind).filter_map do |rel|
186
+ [verb, rel] if layers.nil? || layers.include?(rel.class.layer)
130
187
  end
131
188
  end
132
189
  end
@@ -57,8 +57,9 @@ class Archsight::Resources::Analysis < Archsight::Resources::Base
57
57
 
58
58
  # Description
59
59
  annotation "analysis/description",
60
- description: "Human-readable description of what this analysis validates",
61
- title: "Description"
60
+ description: "Human-readable description of what this analysis validates (markdown)",
61
+ title: "Description",
62
+ format: :markdown
62
63
 
63
64
  # Execution configuration
64
65
  annotation "analysis/timeout",
@@ -25,6 +25,46 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
25
25
  - Frontend applications
26
26
  - Background workers
27
27
 
28
+ ## Choosing this kind
29
+
30
+ **Question:** What is built, deployed and versioned?
31
+ **Test:** It has artifacts and a `component/type`, and can be deployed or linked.
32
+ **Name:** The part, by runtime role (API, Operator, Worker) as in "Payments:API", not by repository category
33
+ ("Testing", "Tools", "Infrastructure"). Test, tooling and infrastructure-as-code repositories belong to the
34
+ component they build, test or deploy.
35
+
36
+ A component belongs to exactly one service. See the modeling guide, "Choosing the Kind".
37
+
38
+ ## Component types
39
+
40
+ Set `component/type` to say what the component is built as; importers set it from the manifests:
41
+
42
+ - `executable`: has an entry point and is run or deployed (services, workers, operators, command-line tools)
43
+ - `library`: imported by other components and not run on its own (shared libraries, SDKs)
44
+ - `module`: a unit of versioning or dependency that bundles executables and importable libraries (a Go module
45
+ with `cmd/` and shared packages, a Maven parent, an npm workspace root)
46
+ - `plugin`: loaded by a host program (kubectl, Terraform, Grafana)
47
+ - `frontend`: runs in the browser
48
+ - `other`
49
+
50
+ `component/role` refines an executable or plugin (`service`, `cli`, `job`, `operator`, `agent`); importers cannot
51
+ tell these apart, so it is set by hand and never overwritten. Importers also add machine tags
52
+ (`namespace:value`) to the regular `architecture/tags`: `ecosystem:go`, `packaging:go-module`,
53
+ `linkage:shared`; the tags a person set stay, and a re-import only replaces the machine tags of these
54
+ namespaces. The entry points (main packages such as `cmd/api`) go to `component/entrypoints`, a list that
55
+ works in queries like a tag but is not a sidebar filter, as there are too many distinct values. Deployable forms (container image, chart, package) are `TechnologyArtifact`s, not
56
+ component types.
57
+
58
+ ## Where it runs
59
+
60
+ A component is `servedBy` the `TechnologyService` it is deployed on (a product's cluster, a managed platform) and
61
+ `servedBy` the `TechnologySystemSoftware` it runs on (the runtime, such as a Kubernetes distribution). Deployment
62
+ importers write the service per component, so a sidecar or agent deployed into many clusters shows where it runs
63
+ without making its own service look like it runs in all of them.
64
+
65
+ The computed `component/dependents` counts the components that depend on this one, which tells shared libraries
66
+ and modules from components nobody consumes.
67
+
28
68
  ## Security and risk modelling
29
69
 
30
70
  - **Asset at risk:** set `asset/value` and the protection needs; `assesses` from a vulnerability or risk.
@@ -46,6 +86,18 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
46
86
  title: "Architecture Size",
47
87
  enum: %w[microservice monolith]
48
88
 
89
+ # Classification
90
+ annotation "component/type",
91
+ description: "What the component is built as (executable, library, module, ...)",
92
+ title: "Component Type",
93
+ enum: %w[executable library module plugin frontend other],
94
+ filter: :word
95
+ annotation "component/role",
96
+ description: "What an executable or plugin does; set by hand",
97
+ title: "Component Role",
98
+ enum: %w[service cli job operator agent],
99
+ filter: :word
100
+
49
101
  # Availability
50
102
  annotation "availability/quality",
51
103
  description: "Service availability quality target per year",
@@ -129,6 +181,12 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
129
181
  enum: ["no support", "support by dev", "support by ops", "support by api",
130
182
  "self service support via a frontend"]
131
183
 
184
+ annotation "component/entrypoints",
185
+ description: "Comma-separated entry points (main packages or commands such as cmd/api), set by importers",
186
+ title: "Entry points",
187
+ sidebar: false,
188
+ filter: :list
189
+
132
190
  # Deployment (from ArgoCD/cluster)
133
191
  annotation "deployment/images",
134
192
  description: "Deployed container images",
@@ -136,20 +194,21 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
136
194
  sidebar: false,
137
195
  filter: :list
138
196
  annotation "deployment/chart",
139
- description: "Deployed Helm chart name",
197
+ description: "Deployed Helm charts, comma separated",
140
198
  title: "Helm Chart",
141
199
  sidebar: false,
142
- filter: :word
200
+ filter: :list
143
201
  annotation "deployment/namespace",
144
- description: "Kubernetes namespace",
202
+ description: "Kubernetes namespaces, comma separated",
145
203
  title: "Namespace",
146
204
  sidebar: false,
147
- filter: :word
205
+ filter: :list
148
206
  annotation "deployment/cluster",
149
- description: "Target cluster name",
207
+ description: "Target clusters, comma separated",
150
208
  title: "Cluster",
151
- filter: :word,
152
- summary: true
209
+ filter: :list,
210
+ summary: true,
211
+ deprecated: "list the clusters as spec.servedBy.technologyServices"
153
212
 
154
213
  # Computed Annotations
155
214
  computed_annotation "repository/artifacts/total",
@@ -197,6 +256,14 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
197
256
  end
198
257
  end
199
258
 
259
+ computed_annotation "component/dependents",
260
+ title: "Dependents",
261
+ description: "Number of components that depend directly on this one",
262
+ type: Integer,
263
+ sidebar: true do
264
+ count(incoming(:ApplicationComponent))
265
+ end
266
+
200
267
  computed_annotation "scc/language",
201
268
  title: "Primary Language",
202
269
  summary: true,
@@ -350,6 +417,7 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
350
417
  relation :realizedThrough, :technologyArtifacts, :TechnologyArtifact
351
418
  relation :realizedBy, :technologyComponents, :TechnologyNode
352
419
  relation :servedBy, :technologyComponents, :TechnologySystemSoftware
420
+ relation :servedBy, :technologyServices, :TechnologyService
353
421
  relation :exposes, :applicationInterfaces, :ApplicationInterface
354
422
  relation :dependsOn, :applicationInterfaces, :ApplicationInterface
355
423
  relation :dependsOn, :applicationComponents, :ApplicationComponent
@@ -25,6 +25,17 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
25
25
  - gRPC services
26
26
  - Message queues (producer/consumer interfaces)
27
27
  - WebSocket endpoints
28
+
29
+ ## Reachability
30
+
31
+ An interface is `servedBy` the `TechnologyInterface` that carries it (a protocol, an authentication method) and
32
+ by the `TechnologyCommunicationNetwork`s on which it can be reached, just as a `TechnologyNode` attaches to the
33
+ networks it is on. Together with `api/authenticationMethod` this decides the exposure of the interface: an
34
+ interface without authentication on an internal network is a different risk than on a network that
35
+ internet-facing components attach to. The network page lists the interfaces as incoming "served by".
36
+
37
+ Write the networks only when they are known. An interface without a network makes no claim about where it can
38
+ be reached.
28
39
  MD
29
40
 
30
41
  icon "usb"
@@ -47,6 +58,7 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
47
58
  enum: ["none", "hard coded", "pbac", "abac", "rbac"]
48
59
 
49
60
  relation :servedBy, :technologyComponents, :TechnologyInterface
61
+ relation :servedBy, :technologyCommunicationNetworks, :TechnologyCommunicationNetwork
50
62
  relation :realizes, :motivationConstraints, :MotivationConstraint
51
63
  relation :serves, :dataObjects, :DataObject
52
64
  end
@@ -20,11 +20,21 @@ class Archsight::Resources::ApplicationService < Archsight::Resources::Base
20
20
 
21
21
  Use ApplicationService to represent:
22
22
 
23
- - Complete product offerings (ManagedKubernetes, DBaaS PostgreSQL)
23
+ - Behaviour software offers to a user or another system ("Payment Authorization", "Document Search")
24
24
  - Logical groupings of application components
25
25
  - Services exposed to business processes
26
26
  - APIs and their implementations as a cohesive unit
27
27
 
28
+ ## Choosing this kind
29
+
30
+ **Question:** What behaviour does software offer a consumer?
31
+ **Test:** It can be described without naming repositories, deployments or runtime objects, and has one owning actor.
32
+ **Name:** The function, not the technology or a deployment object ("Payment Authorization").
33
+
34
+ A component belongs to exactly one service. One service using another is `dependsOn` between the two services, not
35
+ the other service's components listed again; a service that many others use is modeled once. A service whose only component has the same name is a smell. See
36
+ the modeling guide, "Choosing the Kind".
37
+
28
38
  ## Security and risk modelling
29
39
 
30
40
  A service is an asset at risk (`asset/*` profile, protection needs) that loss events `affects`, a trigger target
@@ -217,6 +227,7 @@ class Archsight::Resources::ApplicationService < Archsight::Resources::Base
217
227
  end
218
228
 
219
229
  relation :realizedThrough, :applicationComponents, :ApplicationComponent
230
+ relation :dependsOn, :applicationServices, :ApplicationService
220
231
  relation :servedBy, :businessActors, :BusinessActor
221
232
  relation :servedBy, :technologyServices, :TechnologyService
222
233
  relation :realizes, :motivationConstraints, :MotivationConstraint
@@ -35,12 +35,20 @@ module Archsight
35
35
  # A kind may mark at most this many annotations as summary (see Base.annotation)
36
36
  MAX_SUMMARY_ANNOTATIONS = 3
37
37
 
38
+ # The options `annotation` accepts (see Archsight::Annotations::Annotation)
39
+ ANNOTATION_OPTIONS = %i[description filter title format enum sidebar type summary editor validator unique
40
+ deprecated default].freeze
41
+
38
42
  # Define an annotation using the Annotation class
39
43
  # `summary: true` marks it as a summary attribute: it is returned with every search hit of this kind.
40
- def self.annotation(key, description: nil, filter: nil, title: nil, format: nil, enum: nil, sidebar: true,
41
- type: nil, summary: false, editor: true, validator: nil)
42
- options = { description: description, filter: filter, title: title, format: format, enum: enum,
43
- sidebar: sidebar, type: type, summary: summary, editor: editor, validator: validator }
44
+ # `unique: true` makes it an identifier: its values (each element of a list) are unique among the resources
45
+ # of the kind, which `archsight lint` checks, and its values are shown as chips without links (not tags).
46
+ # `deprecated: "what to use instead"` keeps it working while `archsight lint` lists every use.
47
+ # `default: "value"` is the value of a resource that does not set it (see #apply_annotation_defaults!).
48
+ def self.annotation(key, **options)
49
+ unknown = options.keys - ANNOTATION_OPTIONS
50
+ raise ArgumentError, "unknown annotation option(s) #{unknown.join(", ")} for #{key}" if unknown.any?
51
+
44
52
  register_annotation(Archsight::Annotations::Annotation.new(key, options))
45
53
  end
46
54
 
@@ -143,8 +151,11 @@ module Archsight
143
151
  end
144
152
  end
145
153
 
154
+ # The layer of the kind, one of Archsight::Resources::LAYERS (default `other`)
146
155
  def self.layer(layer_name = nil)
147
156
  if layer_name
157
+ raise ArgumentError, "unknown layer #{layer_name.inspect} for #{name}" unless Archsight::Resources::LAYERS.include?(layer_name)
158
+
148
159
  @layer = layer_name
149
160
  else
150
161
  @layer || "other" # default layer
@@ -223,6 +234,26 @@ module Archsight
223
234
  @raw["metadata"]["annotations"][key] = value
224
235
  end
225
236
 
237
+ # Gives every annotation with a `default:` that the file does not set its default, in the annotations hash like
238
+ # a written value, so queries, filters and the page see it. The editor leaves these out (#defaulted?), so a
239
+ # save does not write them into the file.
240
+ def apply_annotation_defaults!
241
+ self.class.annotations.each do |annotation|
242
+ next if annotation.default.nil? || annotation.pattern? || annotations.key?(annotation.key)
243
+
244
+ defaulted = (@defaulted ||= []) #: Array[String]
245
+ defaulted << annotation.key
246
+ @raw["metadata"] ||= {}
247
+ @raw["metadata"]["annotations"] = @raw["metadata"]["annotations"] || {} #: Hash[String, untyped]
248
+ @raw["metadata"]["annotations"][annotation.key] = annotation.default
249
+ end
250
+ end
251
+
252
+ # Whether the value of the annotation is its default, not written in the file
253
+ def defaulted?(key)
254
+ @defaulted&.include?(key) || false
255
+ end
256
+
226
257
  # Get a computed annotation value from the cache
227
258
  # @param key [String] The annotation key
228
259
  # @return [Object, nil] The computed value or nil
@@ -338,7 +369,29 @@ module Archsight
338
369
 
339
370
  def merge!(inst)
340
371
  # NOTE: path reference is preserved from the original instance
341
- @raw = Archsight::Helpers.deep_merge(@raw, inst.raw)
372
+ merged = Archsight::Helpers.deep_merge(@raw, inst.raw)
373
+ union_list_annotations!(merged, inst.raw)
374
+ @raw = merged
375
+ end
376
+
377
+ # Annotations hold strings, which replace each other on merge. The values of a list annotation (tags, aliases)
378
+ # are joined instead, so two documents of one resource can each add to it.
379
+ def union_list_annotations!(merged, other_raw)
380
+ mine = @raw.dig("metadata", "annotations")
381
+ theirs = other_raw.dig("metadata", "annotations")
382
+ return unless mine.is_a?(Hash) && theirs.is_a?(Hash)
383
+
384
+ theirs.each do |key, value|
385
+ next unless mine[key].is_a?(String) && value.is_a?(String)
386
+ next unless self.class.annotation_matching(key)&.list?
387
+
388
+ values = (split_list(mine[key]) + split_list(value)).uniq
389
+ merged["metadata"]["annotations"][key] = values.join(",")
390
+ end
391
+ end
392
+
393
+ def split_list(text)
394
+ text.split(/,|\n/).map(&:strip).reject(&:empty?)
342
395
  end
343
396
 
344
397
  # raise provides a helper for better error messages including current path and line no
@@ -25,6 +25,11 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
25
25
  - External vendors or partners
26
26
  - Support organizations
27
27
  - Cross-functional groups
28
+ - Customers and providers: the parties a `BusinessContract` is `agreedBy`
29
+ - The party behind a `MotivationStakeholder`: the stakeholder is `representedBy` it
30
+ - The team behind infrastructure: a `TechnologyDevice`, `TechnologyEquipment`, `TechnologyNode` or
31
+ `TechnologyCommunicationNetwork` is `ownedBy` the team that operates it, and a device, equipment or node is
32
+ `servedBy` the team it is dedicated to (a test server, a cluster partition)
28
33
 
29
34
  ## Security and risk modelling
30
35
 
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BusinessContract represents an agreement between a provider and a consumer
4
+ class Archsight::Resources::BusinessContract < Archsight::Resources::Base
5
+ include_annotations :git, :architecture, :risk
6
+
7
+ description <<~MD
8
+ Represents an agreement between a provider and a consumer: the rights and obligations both agreed to.
9
+
10
+ ## ArchiMate Definition
11
+
12
+ **Layer:** Business
13
+ **Aspect:** Passive Structure
14
+
15
+ A contract is a formal or informal specification of an agreement between a provider and a consumer that
16
+ specifies rights and obligations. It is a specialization of a business object. A product is accompanied by a
17
+ contract, and a contract can consist of parts.
18
+
19
+ ## Usage
20
+
21
+ Use BusinessContract to represent:
22
+
23
+ - Framework contracts with a customer
24
+ - Call-offs under a framework contract (an order for one more block of capacity)
25
+ - Service level agreements
26
+ - Amendments of a contract
27
+
28
+ A contract is a `BusinessContract`, not a `DataObject` and not text in a description: only then do the graph,
29
+ views and analyses see what it agrees, who agreed it and what delivered it.
30
+
31
+ ## How it connects
32
+
33
+ - A `BusinessProduct` `aggregates` the contract that comes with it
34
+ - It is `agreedBy` its parties: the actual organisations (the customer, the provider), not a generic role
35
+ - It is `composedOf` its parts: call-offs, SLAs and amendments, which are contracts themselves (no cycles)
36
+ - It `covers` what it obliges the provider to deliver: equipment, nodes, technology and application services
37
+ - It is `realizedThrough` the `ImplementationDeliverable` that fulfils it; the `ImplementationWorkPackage` that
38
+ `realizes` the deliverable `affects` the equipment it installs
39
+
40
+ The other side shows these as incoming relations: the contracts an actor agreed, what covers a piece of
41
+ equipment or a service, the contract a deliverable fulfils.
42
+ MD
43
+
44
+ icon "journal-page"
45
+ layer "business"
46
+
47
+ annotation "contract/type",
48
+ description: "What kind of contract this is",
49
+ enum: %w[framework call-off sla amendment other],
50
+ filter: :word,
51
+ summary: true
52
+ annotation "contract/reference",
53
+ description: "The contract number",
54
+ title: "Reference",
55
+ summary: true
56
+ annotation "contract/from",
57
+ description: "When the contract starts (ISO 8601 date or time)",
58
+ title: "From",
59
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
60
+ annotation "contract/until",
61
+ description: "When the contract ends (ISO 8601 date or time)",
62
+ title: "Until",
63
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
64
+
65
+ relation :agreedBy, :businessActors, :BusinessActor
66
+ relation :composedOf, :businessContracts, :BusinessContract
67
+ relation :covers, :technologyEquipment, :TechnologyEquipment
68
+ relation :covers, :technologyNodes, :TechnologyNode
69
+ relation :covers, :technologyServices, :TechnologyService
70
+ relation :covers, :applicationServices, :ApplicationService
71
+ relation :realizedThrough, :implementationDeliverables, :ImplementationDeliverable
72
+ end
@@ -25,6 +25,17 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
25
25
  - Kubernetes services
26
26
  - Enterprise support packages
27
27
  - API products
28
+
29
+ ## Choosing this kind
30
+
31
+ **Question:** What does a customer buy or contract?
32
+ **Test:** It appears on a price list, catalog or customer documentation.
33
+ **Name:** The market name ("Online Checkout").
34
+
35
+ A product `realizes` a capability and is `servedBy` the services that deliver it. A product that nearly every
36
+ service serves is a catalog, not a product. See the modeling guide, "Choosing the Kind".
37
+
38
+ The contract that comes with the product is a `BusinessContract` the product `aggregates`.
28
39
  MD
29
40
 
30
41
  icon "box-iso"
@@ -204,4 +215,5 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
204
215
  relation :servedBy, :applicationServices, :ApplicationService
205
216
  relation :exposes, :applicationInterfaces, :ApplicationInterface
206
217
  relation :provides, :complianceEvidences, :ComplianceEvidence
218
+ relation :aggregates, :businessContracts, :BusinessContract
207
219
  end
@@ -37,6 +37,7 @@ class Archsight::Resources::BusinessRole < Archsight::Resources::Base
37
37
  - A `BusinessProcess` is `performedBy` the role, a `BusinessControl` is `ownedBy` and `executedBy` it
38
38
  - Assessments, principles, work packages, deliverables and implementation events are `ownedBy` it
39
39
  - The role is `performedBy` the actors that hold it
40
+ - A `MotivationStakeholder` can be `representedBy` the role (the interest belongs to the role, whoever holds it)
40
41
  - Pointing straight at an actor stays valid; use a role where the responsibility has a name of its own
41
42
 
42
43
  ## Security and risk modelling
@@ -26,6 +26,7 @@ class Archsight::Resources::ImplementationDeliverable < Archsight::Resources::Ba
26
26
  ## How it connects
27
27
 
28
28
  - A `ImplementationWorkPackage` `realizes` it
29
+ - A `BusinessContract` is `realizedThrough` it: the delivery that fulfils the contract (a call-off)
29
30
  - It is `ownedBy` an actor and `realizes` plateaus, requirements, goals, outcomes and `ComplianceEvidence`
30
31
 
31
32
  ## Security and risk modelling
@@ -25,9 +25,12 @@ class Archsight::Resources::ImplementationPlateau < Archsight::Resources::Base
25
25
 
26
26
  ## How it connects
27
27
 
28
- - A plateau `contains` the components, services, nodes, processes and requirements that belong to it
28
+ - A plateau `contains` the application components and services, technology nodes, services, system
29
+ software and artifacts, business processes and requirements that belong to it. Listing the technology
30
+ elements lets a platform migration (platform A to platform B) be described state by state
29
31
  - It `triggers` the next plateau (migration path)
30
- - A `ImplementationDeliverable` `realizes` it; a `ImplementationGap` `compares` two plateaus
32
+ - A `ImplementationDeliverable` `realizes` it; a `ImplementationGap` `compares` two plateaus, so
33
+ the elements added, kept or removed between them show up per plateau
31
34
  MD
32
35
 
33
36
  icon "packages"
@@ -55,6 +58,9 @@ class Archsight::Resources::ImplementationPlateau < Archsight::Resources::Base
55
58
  relation :contains, :applicationComponents, :ApplicationComponent
56
59
  relation :contains, :applicationServices, :ApplicationService
57
60
  relation :contains, :technologyNodes, :TechnologyNode
61
+ relation :contains, :technologyServices, :TechnologyService
62
+ relation :contains, :technologySystemSoftware, :TechnologySystemSoftware
63
+ relation :contains, :technologyArtifacts, :TechnologyArtifact
58
64
  relation :contains, :businessProcesses, :BusinessProcess
59
65
  relation :contains, :motivationRequirements, :MotivationRequirement
60
66
  relation :triggers, :implementationPlateaus, :ImplementationPlateau
@@ -28,7 +28,7 @@ class Archsight::Resources::ImplementationWorkPackage < Archsight::Resources::Ba
28
28
 
29
29
  - `ownedBy` the accountable actor or role, `performedBy` the actors or roles that carry it out
30
30
  - `realizes` deliverables (its results), requirements and goals
31
- - `affects` the assets it changes; the assessments (risks, findings) it treats are `mitigatedBy` it (written on the assessment)
31
+ - `affects` the assets it changes, for example the `TechnologyEquipment` a project installs; the assessments (risks, findings) it treats are `mitigatedBy` it (written on the assessment)
32
32
  - `triggers` the work packages that follow it; an `ImplementationEvent` can trigger it
33
33
  - A `ImplementationGap` is `closedBy` it
34
34
 
@@ -79,5 +79,6 @@ class Archsight::Resources::ImplementationWorkPackage < Archsight::Resources::Ba
79
79
  relation :affects, :applicationComponents, :ApplicationComponent
80
80
  relation :affects, :applicationServices, :ApplicationService
81
81
  relation :affects, :technologyNodes, :TechnologyNode
82
+ relation :affects, :technologyEquipment, :TechnologyEquipment
82
83
  relation :triggers, :implementationWorkPackages, :ImplementationWorkPackage
83
84
  end
@@ -35,26 +35,52 @@ class Archsight::Resources::Import < Archsight::Resources::Base
35
35
  icon "download"
36
36
  layer "other"
37
37
 
38
- # Handler selection
38
+ # Handler selection: a built-in handler or one the resources repository registers (import/handlerPath)
39
39
  annotation "import/handler",
40
- description: "Handler class name to execute this import",
40
+ description: "Name of the handler that executes this import: a built-in one or one registered by the file " \
41
+ "in import/handlerPath",
41
42
  title: "Handler",
42
43
  summary: true,
43
- enum: %w[
44
- gitlab github repository
45
- rest-api rest-api-index
46
- jira-discover jira-metrics
47
- go-grapher go-dep-resolver
48
- cpp-grapher ruby-grapher python-grapher
49
- java-grapher javascript-grapher
50
- rust-grapher crystal-grapher elixir-grapher
51
- ]
44
+ filter: :word,
45
+ validator: lambda { |value|
46
+ require "archsight/import/handler_loader"
47
+ Archsight::Import::HandlerLoader.load_builtin!
48
+ next if Archsight::Import::Registry[value]
49
+
50
+ "unknown import handler '#{value}'. Registered: #{Archsight::Import::Registry.handlers.sort.join(", ")}; " \
51
+ "a handler of the resources repository needs import/handlerPath"
52
+ }
53
+
54
+ annotation "import/handlerPath",
55
+ description: "Ruby file of a handler that lives in the resources repository, relative to the resources " \
56
+ "root (e.g., 'importers/deployments.rb'). It registers the name used in import/handler.",
57
+ title: "Handler Path"
58
+
59
+ annotation "import/dependsOn",
60
+ description: "Imports that must run first, by name or glob pattern (e.g., 'Import:Repo:*'), comma " \
61
+ "separated. Adds to the dependency on the import that generated this one.",
62
+ title: "Depends On",
63
+ filter: :list,
64
+ sidebar: false
52
65
 
53
66
  # Output configuration
54
67
  annotation "import/outputPath",
55
68
  description: "Output file path relative to resources root (e.g., 'imports/generated/gitlab.yaml')",
56
69
  title: "Output Path"
57
70
 
71
+ annotation "generated/inputsHash",
72
+ description: "Hash of the files matched by import/config/inputs at the last run (for change detection)",
73
+ title: "Inputs Hash",
74
+ sidebar: false,
75
+ editor: false
76
+
77
+ annotation "generated/outputs",
78
+ description: "Files the last run wrote, relative to the resources root",
79
+ title: "Outputs",
80
+ filter: :list,
81
+ sidebar: false,
82
+ editor: false
83
+
58
84
  # Control flags
59
85
  annotation "import/enabled",
60
86
  description: "Whether this import is enabled",