archsight 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -1
  3. data/README.md +6 -1
  4. data/docs/icons.md +3 -2
  5. data/docs/index.md.erb +5 -4
  6. data/docs/modeling.md +122 -5
  7. data/docs/pages.md +12 -3
  8. data/docs/search.md +9 -2
  9. data/docs/togaf.md +4 -0
  10. data/lib/archsight/annotations/relation_resolver.rb +15 -6
  11. data/lib/archsight/cli.rb +6 -0
  12. data/lib/archsight/database.rb +57 -3
  13. data/lib/archsight/diagram.rb +7 -0
  14. data/lib/archsight/documentation.rb +7 -4
  15. data/lib/archsight/editor.rb +2 -2
  16. data/lib/archsight/helpers/requirements_blocks.rb +1 -1
  17. data/lib/archsight/helpers/resource_resolver.rb +1 -1
  18. data/lib/archsight/helpers/wiki_links.rb +11 -0
  19. data/lib/archsight/linter.rb +6 -0
  20. data/lib/archsight/mcp/analyze_resource_tool.rb +2 -2
  21. data/lib/archsight/mcp/resource_doc_tool.rb +2 -2
  22. data/lib/archsight/query/ast.rb +2 -1
  23. data/lib/archsight/query/evaluator.rb +2 -2
  24. data/lib/archsight/references.rb +129 -0
  25. data/lib/archsight/requirements.rb +4 -4
  26. data/lib/archsight/resources/application_component.rb +3 -0
  27. data/lib/archsight/resources/application_interface.rb +1 -1
  28. data/lib/archsight/resources/application_service.rb +4 -4
  29. data/lib/archsight/resources/base.rb +40 -5
  30. data/lib/archsight/resources/business_control.rb +80 -0
  31. data/lib/archsight/resources/business_process.rb +3 -2
  32. data/lib/archsight/resources/business_product.rb +2 -2
  33. data/lib/archsight/resources/compliance_evidence.rb +36 -1
  34. data/lib/archsight/resources/data_object.rb +1 -1
  35. data/lib/archsight/resources/{business_constraint.rb → motivation_constraint.rb} +4 -4
  36. data/lib/archsight/resources/motivation_goal.rb +1 -1
  37. data/lib/archsight/resources/{business_requirement.rb → motivation_requirement.rb} +15 -5
  38. data/lib/archsight/resources/motivation_stakeholder.rb +2 -2
  39. data/lib/archsight/resources/strategy_capability.rb +2 -2
  40. data/lib/archsight/resources/technology_node.rb +1 -1
  41. data/lib/archsight/resources/technology_service.rb +3 -3
  42. data/lib/archsight/resources/technology_system_software.rb +3 -3
  43. data/lib/archsight/resources.rb +34 -3
  44. data/lib/archsight/template.rb +2 -2
  45. data/lib/archsight/version.rb +1 -1
  46. data/lib/archsight/web/api/docs.rb +1 -1
  47. data/lib/archsight/web/api/json_helpers.rb +14 -10
  48. data/lib/archsight/web/api/openapi/spec.yaml +2 -2
  49. data/lib/archsight/web/api/page_helpers.rb +8 -7
  50. data/lib/archsight/web/api/routes.rb +1 -1
  51. data/lib/archsight/web/application.rb +6 -1
  52. data/lib/archsight/web/public/vue/{ApiDocsPage-D-cPRZCT.js → ApiDocsPage-DoOxKjG0.js} +1 -1
  53. data/lib/archsight/web/public/vue/{DocPage-DK6vNDbF.js → DocPage-CfsC3CeQ.js} +1 -1
  54. data/lib/archsight/web/public/vue/{EditorPage-BoJpQaVw.js → EditorPage-CsJA0q8n.js} +1 -1
  55. data/lib/archsight/web/public/vue/{ErrorPage-PGyjdtEf.js → ErrorPage-OJpJz9df.js} +1 -1
  56. data/lib/archsight/web/public/vue/{GraphView-BLiKR4zP.js → GraphView-CBq6oFRV.js} +1 -1
  57. data/lib/archsight/web/public/vue/HomePage-BwgMLgVC.js +2 -0
  58. data/lib/archsight/web/public/vue/InstanceRouter-DQz-SsQq.js +1 -0
  59. data/lib/archsight/web/public/vue/{InstanceRouter-60Tt3ZNM.css → InstanceRouter-jeavM6PW.css} +1 -1
  60. data/lib/archsight/web/public/vue/KindList-4q0K9Cll.js +1 -0
  61. data/lib/archsight/web/public/vue/{PageView-9MgHtrgl.js → PageView-DzeGnvoS.js} +1 -1
  62. data/lib/archsight/web/public/vue/{QueryError-D1FL1xgA.js → QueryError-kg6Yf-pW.js} +1 -1
  63. data/lib/archsight/web/public/vue/ResourceList-Df_0e0qt.js +2 -0
  64. data/lib/archsight/web/public/vue/SearchResults-CKgY6_wH.js +1 -0
  65. data/lib/archsight/web/public/vue/{SearchResults-DiW5XVYW.css → SearchResults-CyPUZZSC.css} +1 -1
  66. data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +1 -0
  67. data/lib/archsight/web/public/vue/{WikiPage-CeCQBTDS.js → WikiPage-DrvCHwJ7.js} +3 -3
  68. data/lib/archsight/web/public/vue/{index-D7m61Ahx.js → index-D0Q5GZRs.js} +2 -2
  69. data/lib/archsight/web/public/vue/index-oF-iyZvk.css +1 -0
  70. data/lib/archsight/web/public/vue.html +2 -2
  71. metadata +23 -21
  72. data/lib/archsight/web/public/vue/HomePage-C0lR8i2C.js +0 -2
  73. data/lib/archsight/web/public/vue/InstanceRouter-D3W2jJHV.js +0 -1
  74. data/lib/archsight/web/public/vue/KindList-BlsaRBNO.js +0 -1
  75. data/lib/archsight/web/public/vue/ResourceList-vkgFyeOY.js +0 -2
  76. data/lib/archsight/web/public/vue/SearchResults-Cl3O_OEr.js +0 -1
  77. data/lib/archsight/web/public/vue/WikiPage-C-8SG67a.css +0 -1
  78. data/lib/archsight/web/public/vue/index-Dbx3MXWG.css +0 -1
@@ -26,6 +26,12 @@ module Archsight
26
26
  @errors
27
27
  end
28
28
 
29
+ # Things that still work but should be changed: renamed kinds and relation keys that the files still use.
30
+ # They are not errors, so they do not make `archsight lint` fail.
31
+ def warnings
32
+ @database.respond_to?(:deprecations) ? @database.deprecations.map(&:to_s) : []
33
+ end
34
+
29
35
  private
30
36
 
31
37
  # Pages need exactly one menu and their [[links]] must resolve
@@ -24,13 +24,13 @@ class Archsight::MCP::AnalyzeResourceTool < FastMcp::Tool
24
24
  Example: kind="ApplicationInterface", name="Kubernetes:RestAPI", impact=true
25
25
 
26
26
  RESOURCE KINDS: TechnologyArtifact, ApplicationComponent, ApplicationInterface,
27
- ApplicationService, BusinessRequirement, ComplianceEvidence, and more.
27
+ ApplicationService, MotivationRequirement, ComplianceEvidence, and more.
28
28
  DESC
29
29
  arguments do
30
30
  required(:kind).filled(:string).description(
31
31
  "Resource type to analyze. Common kinds: TechnologyArtifact (repos, code), " \
32
32
  "ApplicationComponent (services), ApplicationInterface (APIs), " \
33
- "BusinessRequirement (compliance controls), ComplianceEvidence (compliance proof)"
33
+ "MotivationRequirement (compliance and legal requirements), ComplianceEvidence (compliance proof)"
34
34
  )
35
35
  required(:name).filled(:string).description(
36
36
  "Exact name of the resource (case-sensitive). Use QueryTool first if unsure of exact name."
@@ -23,7 +23,7 @@ class Archsight::MCP::ResourceDocTool < FastMcp::Tool
23
23
  • ApplicationComponent - Services and application building blocks
24
24
  • ApplicationInterface - APIs and interfaces exposed by components
25
25
  • ApplicationService - Business services provided by applications
26
- • BusinessRequirement - Compliance controls and business requirements
26
+ • MotivationRequirement - Statements of need, such as compliance and legal requirements
27
27
  • ComplianceEvidence - Evidence linking artifacts to compliance requirements
28
28
  DESC
29
29
  arguments do
@@ -61,7 +61,7 @@ class Archsight::MCP::ResourceDocTool < FastMcp::Tool
61
61
  kind: kind_symbol.to_s,
62
62
  description: klass.description || "No description available",
63
63
  annotation_count: klass.annotations.count,
64
- relation_count: klass.relations.count
64
+ relation_count: klass.declared_relations.count
65
65
  }
66
66
  end
67
67
 
@@ -218,7 +218,8 @@ module Archsight::Query::AST
218
218
  attr_reader :kind_name
219
219
 
220
220
  def initialize(kind_name)
221
- @kind_name = kind_name
221
+ # a renamed kind keeps matching under its old name
222
+ @kind_name = Archsight::Resources.canonical(kind_name)
222
223
  end
223
224
  end
224
225
 
@@ -292,7 +292,7 @@ class Archsight::Query::Evaluator
292
292
 
293
293
  case node.operator
294
294
  when "=="
295
- instance_kind == node.value.value.to_s
295
+ instance_kind == Archsight::Resources.canonical(node.value.value)
296
296
  when "=~"
297
297
  regex = build_regex_from_value(node.value)
298
298
  !!(instance_kind =~ regex)
@@ -303,7 +303,7 @@ class Archsight::Query::Evaluator
303
303
 
304
304
  def evaluate_kind_in_condition(node, instance)
305
305
  instance_kind = instance.class.to_s.split("::").last
306
- query_values = node.values.map { |v| v.value.to_s }
306
+ query_values = node.values.map { |v| Archsight::Resources.canonical(v.value) }
307
307
  query_values.include?(instance_kind)
308
308
  end
309
309
 
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archsight
4
+ # References derives relations from what the text and the diagrams of resources say, so that nobody has to repeat in
5
+ # YAML what is already written:
6
+ #
7
+ # - `mentions`: `[[Target]]`, `[[Kind/Name]]` and `![[View/Name]]` in a markdown text (a page's content, the
8
+ # description of any resource, any annotation a kind defines as markdown)
9
+ # - `depicts`: the `resource "..."` of the nodes of a diagram: an ```asd block in such a text, an `.asd` file embedded
10
+ # with `![](file.asd)`, and the `architecture/diagram` annotation of any resource
11
+ #
12
+ # The relations are added to the resources like written ones (see Base#add_derived_relation), after the written ones
13
+ # are resolved, and are rebuilt on every load. Only what names a resource exactly counts: a target that is missing,
14
+ # ambiguous or only matches part of a name is skipped (the linter reports the broken ones), and so are links in code.
15
+ # Views and requirements blocks select resources with a query when they run, so they name nothing.
16
+ module References
17
+ # A fenced code block, and an inline code span: links in them are shown as written
18
+ FENCE = /^[ \t]*(?:```|~~~).*?^[ \t]*(?:```|~~~)[ \t]*$/m
19
+ INLINE_CODE = /`[^`\n]*`/
20
+
21
+ # Annotations that are markdown besides those a kind defines with `format: :markdown`
22
+ MARKDOWN_KEYS = %w[page/content architecture/description].freeze
23
+
24
+ module_function
25
+
26
+ # Adds the derived relations to every resource of the database.
27
+ def derive!(database)
28
+ finder = Finder.new(database)
29
+ database.instances.values.flat_map(&:values).each do |inst|
30
+ text_sources(inst).each { |markdown| mentions(markdown, finder).each { |target| link(inst, :mentions, target) } }
31
+ diagram_sources(inst, database).each { |source| depictions(source, finder).each { |target| link(inst, :depicts, target) } }
32
+ end
33
+ end
34
+
35
+ # The resources a markdown text mentions with `[[...]]` links and `![[View/...]]` embeds, outside code.
36
+ # @return [Array<Base>]
37
+ def mentions(markdown, finder)
38
+ visible = markdown.to_s.gsub(FENCE, "").gsub(INLINE_CODE, "")
39
+ embeds = visible.scan(Helpers::Embeds::PATTERN).flatten.map(&:strip).filter_map { |reference| finder.embed(reference) }
40
+ targets = visible.gsub(Helpers::Embeds::PATTERN, "").scan(Helpers::WikiLinks::PATTERN).map { |match| match.first.strip }
41
+ (embeds + targets.filter_map { |target| finder.link(target) }).uniq
42
+ end
43
+
44
+ # The resources the nodes of a diagram source name with `resource "..."`; a source that is not a valid diagram
45
+ # names none.
46
+ # @return [Array<Base>]
47
+ def depictions(source, finder)
48
+ Diagram.resource_references(source).filter_map { |reference| finder.reference(reference) }
49
+ rescue Diagram::Error
50
+ []
51
+ end
52
+
53
+ # The markdown texts of a resource: a page's content, the description, and every annotation its kind defines as
54
+ # markdown.
55
+ def text_sources(inst)
56
+ inst.annotations.filter_map do |key, value|
57
+ value if value.is_a?(String) && value.include?("[[") && markdown_annotation?(inst, key)
58
+ end
59
+ end
60
+
61
+ # The diagram sources of a resource: ```asd blocks and embedded `.asd` files of its markdown texts, and its
62
+ # annotations that are diagrams (`architecture/diagram`).
63
+ def diagram_sources(inst, database)
64
+ sources = [] #: Array[String]
65
+ inst.annotations.each do |key, value|
66
+ next unless value.is_a?(String)
67
+
68
+ if inst.class.annotation_format(key) == :asd
69
+ sources << value
70
+ elsif markdown_annotation?(inst, key)
71
+ sources.concat(Helpers::DiagramBlocks.sources(value))
72
+ sources.concat(embedded_diagrams(inst, value, database)) if value.include?(".asd")
73
+ end
74
+ end
75
+ sources
76
+ end
77
+
78
+ def markdown_annotation?(inst, key)
79
+ MARKDOWN_KEYS.include?(key) || inst.class.annotation_format(key) == :markdown
80
+ end
81
+
82
+ # The sources of the `.asd` files a markdown text embeds, found next to the file the resource comes from
83
+ def embedded_diagrams(inst, markdown, database)
84
+ resources_dir = database.path.to_s
85
+ base_dir = Assets.base_dir_for(inst, resources_dir: resources_dir)
86
+ return [] unless base_dir
87
+
88
+ Helpers::AssetImages.asd_files(markdown, base_dir: base_dir, resources_dir: resources_dir).filter_map do |_path, file|
89
+ File.read(file) if File.size(file) <= Helpers::AssetImages::MAX_ASD
90
+ end
91
+ rescue SystemCallError
92
+ []
93
+ end
94
+
95
+ def link(inst, verb, target)
96
+ return if target.equal?(inst)
97
+
98
+ inst.add_derived_relation(verb, target)
99
+ end
100
+
101
+ # Finds the resource a reference in a text names, exactly (see WikiLinks#exact_target_for)
102
+ class Finder
103
+ def initialize(database)
104
+ @database = database
105
+ @resolver = Helpers::ResourceResolver.new(database)
106
+ @links = Helpers::WikiLinks.new(database, resolver: @resolver)
107
+ end
108
+
109
+ # A `[[...]]` target: a page by name or title, `Kind/Name`, or a resource name
110
+ def link(target)
111
+ @links.exact_target_for(target)
112
+ end
113
+
114
+ # An `![[Kind/Name]]` embed: only views and analyses can be embedded
115
+ def embed(reference)
116
+ kind = reference.split("/", 2).first
117
+ return unless Helpers::Embeds::KINDS.include?(kind)
118
+
119
+ reference(reference)
120
+ end
121
+
122
+ # A `resource "..."` of a diagram or an embed: `Name` across all kinds, or `Kind/Name`
123
+ def reference(reference)
124
+ found = @resolver.find(reference)
125
+ found.is_a?(Array) ? @database.instances_by_kind(found.first)[found.last] : nil
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Archsight
4
- # The business requirements of a selection of resources, as the "Business Requirements" table of an instance page
5
- # shows them, merged over all resources of the selection: one entry per BusinessRequirement, with the status the
4
+ # The requirements of a selection of resources, as the "Requirements" table of an instance page
5
+ # shows them, merged over all resources of the selection: one entry per MotivationRequirement, with the status the
6
6
  # resources give it (`realizes` = implemented, `partiallyRealizes` = partial, `plans` = planned).
7
7
  #
8
8
  # Requirements.collect(db, of: 'ApplicationService: name =~ "Backup"', priority: ["must"])
@@ -12,7 +12,7 @@ module Archsight
12
12
  # Relation verb -> status, best first
13
13
  STATUSES = { "realizes" => "implemented", "partiallyRealizes" => "partial", "plans" => "planned" }.freeze
14
14
  PRIORITIES = %w[must should may].freeze
15
- RELATION = :businessRequirements
15
+ RELATION = :motivationRequirements
16
16
 
17
17
  module_function
18
18
 
@@ -52,7 +52,7 @@ module Archsight
52
52
  row << cell.new(text: e[:by].map { |b| b[:name] }.join("\n")) if with_by
53
53
  row
54
54
  end
55
- ViewTable::Table.new(title: spec[:title].to_s.empty? ? "Business Requirements" : spec[:title],
55
+ ViewTable::Table.new(title: spec[:title].to_s.empty? ? "Requirements" : spec[:title],
56
56
  columns: ["Status", "Name", "Priority", "Story", ("Realized by" if with_by)].compact, rows: rows, total: entries.length)
57
57
  end
58
58
 
@@ -346,4 +346,7 @@ class Archsight::Resources::ApplicationComponent < Archsight::Resources::Base
346
346
  relation :exposes, :applicationInterfaces, :ApplicationInterface
347
347
  relation :dependsOn, :applicationInterfaces, :ApplicationInterface
348
348
  relation :dependsOn, :applicationComponents, :ApplicationComponent
349
+ relation :realizes, :motivationRequirements, :MotivationRequirement
350
+ relation :plans, :motivationRequirements, :MotivationRequirement
351
+ relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
349
352
  end
@@ -47,6 +47,6 @@ class Archsight::Resources::ApplicationInterface < Archsight::Resources::Base
47
47
  enum: ["none", "hard coded", "pbac", "abac", "rbac"]
48
48
 
49
49
  relation :servedBy, :technologyComponents, :TechnologyInterface
50
- relation :realizes, :businessConstraints, :BusinessConstraint
50
+ relation :realizes, :motivationConstraints, :MotivationConstraint
51
51
  relation :serves, :dataObjects, :DataObject
52
52
  end
@@ -214,10 +214,10 @@ class Archsight::Resources::ApplicationService < Archsight::Resources::Base
214
214
  relation :realizedThrough, :applicationComponents, :ApplicationComponent
215
215
  relation :servedBy, :businessActors, :BusinessActor
216
216
  relation :servedBy, :technologyServices, :TechnologyService
217
- relation :realizes, :businessConstraints, :BusinessConstraint
218
- relation :realizes, :businessRequirements, :BusinessRequirement
219
- relation :partiallyRealizes, :businessRequirements, :BusinessRequirement
220
- relation :plans, :businessRequirements, :BusinessRequirement
217
+ relation :realizes, :motivationConstraints, :MotivationConstraint
218
+ relation :realizes, :motivationRequirements, :MotivationRequirement
219
+ relation :partiallyRealizes, :motivationRequirements, :MotivationRequirement
220
+ relation :plans, :motivationRequirements, :MotivationRequirement
221
221
  relation :realizes, :dataObjects, :DataObject
222
222
  relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
223
223
  end
@@ -15,12 +15,21 @@ module Archsight
15
15
  end
16
16
 
17
17
  def self.relation(verb, kind, klass_name)
18
- @relations ||= [] #: Array[[Symbol, Symbol, String]]
19
- @relations << [verb, kind, klass_name]
18
+ @declared_relations ||= [] #: Array[[Symbol, Symbol, String]]
19
+ @declared_relations << [verb, kind, klass_name]
20
+ @relations = nil
20
21
  end
21
22
 
23
+ # The relations a resource of this kind may be written with in a file (`spec`)
24
+ def self.declared_relations
25
+ @declared_relations || []
26
+ end
27
+
28
+ # Every relation a resource of this kind can have: the declared ones and the derived ones (`mentions`,
29
+ # `depicts`, see Archsight::References). Everything that follows relations (queries, graphs, impact analysis)
30
+ # uses this; what lets a user write or choose relations uses declared_relations.
22
31
  def self.relations
23
- @relations || []
32
+ @relations ||= (declared_relations + Archsight::Resources::DERIVED_RELATIONS).freeze
24
33
  end
25
34
 
26
35
  # A kind may mark at most this many annotations as summary (see Base.annotation)
@@ -111,6 +120,17 @@ module Archsight
111
120
  annotation_matching(key)&.format
112
121
  end
113
122
 
123
+ # The formats this kind defines for the given annotation keys, `{ "evidence/gaps" => "markdown" }` (keys without
124
+ # a format are left out). The frontend shows each value accordingly.
125
+ def self.annotation_formats(keys)
126
+ formats = {} #: Hash[String, String]
127
+ keys.each do |key|
128
+ format = annotation_format(key)
129
+ formats[key] = format.to_s if format
130
+ end
131
+ formats
132
+ end
133
+
114
134
  def self.annotation_enum(key)
115
135
  annotation_matching(key)&.enum
116
136
  end
@@ -231,17 +251,32 @@ module Archsight
231
251
  end
232
252
 
233
253
  def verb_allowed?(verb)
234
- self.class.relations.any? { |v, _, _| v.to_s == verb.to_s }
254
+ self.class.declared_relations.any? { |v, _, _| v.to_s == verb.to_s }
235
255
  end
236
256
 
237
257
  def verb_kind_allowed?(verb, kind)
238
- self.class.relations.any? { |v, k, _| v.to_s == verb.to_s && k.to_s == kind.to_s }
258
+ self.class.declared_relations.any? { |v, k, _| v.to_s == verb.to_s && k.to_s == kind.to_s }
239
259
  end
240
260
 
241
261
  def relations(verb, kind)
242
262
  (spec[verb.to_s] || {})[kind.to_s] || []
243
263
  end
244
264
 
265
+ # Records a relation that is derived from this resource's text or diagram, not written in its file: the target
266
+ # goes into `spec` where the relations of this verb are read (so queries and graphs see it like any other) and
267
+ # learns about this resource as one that refers to it.
268
+ def add_derived_relation(verb, target)
269
+ key = Archsight::Resources::DERIVED_KEY.to_s
270
+ spec_root = (@raw["spec"] ||= {}) #: Hash[String, untyped]
271
+ by_key = (spec_root[verb.to_s] ||= {}) #: Hash[String, Array[Base]]
272
+ by_key[key] ||= [] #: Array[Base]
273
+ targets = by_key.fetch(key)
274
+ return if targets.any? { |known| known.equal?(target) }
275
+
276
+ targets << target
277
+ target.referenced_by(self, verb.to_sym)
278
+ end
279
+
245
280
  def set_relations(verb, kind, rels)
246
281
  spec[verb.to_s][kind.to_s] = rels
247
282
  rels.each { |rel| rel.referenced_by(self, verb) }
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BusinessControl represents a control that guides a business process
4
+ class Archsight::Resources::BusinessControl < Archsight::Resources::Base
5
+ include_annotations :git, :architecture
6
+
7
+ description <<~MD
8
+ Represents a control: a safeguard or decision step with an owner that guides how a business process is carried out.
9
+
10
+ ## ArchiMate / TOGAF Definition
11
+
12
+ **Layer:** Business
13
+ **Aspect:** Behavior (guidance)
14
+
15
+ ArchiMate has no control element. TOGAF's content metamodel has one: a decision-making step with
16
+ accountability and authority, applied to a process or function. A control is therefore modelled as
17
+ a business-layer kind that a `BusinessProcess` is guided by (`guidedBy`).
18
+
19
+ ## Usage
20
+
21
+ Use BusinessControl to represent:
22
+
23
+ - Security and compliance controls (access review, network documentation, backup verification)
24
+ - Operational checks that a process must pass (change approval, four-eyes principle)
25
+ - Controls of a standard or framework (BSI C5, ISO 27001, SOC 2), linked to the requirements they satisfy
26
+
27
+ ## How it connects
28
+
29
+ - A `BusinessProcess` is `guidedBy` the control
30
+ - The control is `ownedBy` the actor that is accountable for it and `executedBy` the actors that carry it out
31
+ - The control `satisfies` requirements and is `evidencedBy` compliance evidence
32
+
33
+ A control addresses requirements from the **process side**. Whether an application implements a requirement is
34
+ stated on the application (`realizes`, `plans`, `evidencedBy`), not on the control. Link evidence to a control
35
+ only for the records the control itself produces (reviews, diagrams, change history, audit logs), and set the
36
+ `evidence/type` of that evidence to `process`, `documentation` or `audit-log`.
37
+
38
+ Put the best practices and the evidence requirements of a control in the description.
39
+ MD
40
+
41
+ icon "shield-search"
42
+ layer "business"
43
+
44
+ annotation "control/id",
45
+ description: "Identifier of the control in its catalogue (e.g. COS-07)",
46
+ title: "Control ID",
47
+ summary: true
48
+
49
+ annotation "control/status",
50
+ description: "Implementation status of the control",
51
+ enum: %w[implemented partial planned not-implemented],
52
+ filter: :word,
53
+ summary: true
54
+
55
+ annotation "control/frequency",
56
+ description: "How often the control is carried out",
57
+ enum: %w[continuous event-based daily weekly monthly quarterly semi-annually annually],
58
+ filter: :word,
59
+ summary: true
60
+
61
+ annotation "control/objective",
62
+ description: "What the control achieves, in one short paragraph",
63
+ title: "Objective",
64
+ format: :markdown
65
+
66
+ annotation "control/last-review",
67
+ description: "When the control was last reviewed (ISO 8601 date or time)",
68
+ title: "Last review",
69
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
70
+
71
+ annotation "control/next-review",
72
+ description: "When the control is due for review (ISO 8601 date or time)",
73
+ title: "Next review",
74
+ validator: ->(value) { Archsight::Resources::Page.timestamp_error(value) }
75
+
76
+ relation :ownedBy, :businessActors, :BusinessActor
77
+ relation :executedBy, :businessActors, :BusinessActor
78
+ relation :satisfies, :motivationRequirements, :MotivationRequirement
79
+ relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
80
+ end
@@ -30,8 +30,9 @@ class Archsight::Resources::BusinessProcess < Archsight::Resources::Base
30
30
  icon "kanban-board"
31
31
  layer "business"
32
32
 
33
- relation :realizes, :businessConstraints, :BusinessConstraint
34
- relation :realizes, :businessRequirements, :BusinessRequirement
33
+ relation :realizes, :motivationConstraints, :MotivationConstraint
34
+ relation :realizes, :motivationRequirements, :MotivationRequirement
35
35
  relation :servedBy, :applicationServices, :ApplicationService
36
36
  relation :performedBy, :businessActors, :BusinessActor
37
+ relation :guidedBy, :businessControls, :BusinessControl
37
38
  end
@@ -198,8 +198,8 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
198
198
  end
199
199
 
200
200
  relation :realizes, :strategyCapabilities, :StrategyCapability
201
- relation :realizes, :businessConstraints, :BusinessConstraint
202
- relation :realizes, :businessRequirements, :BusinessRequirement
201
+ relation :realizes, :motivationConstraints, :MotivationConstraint
202
+ relation :realizes, :motivationRequirements, :MotivationRequirement
203
203
  relation :servedBy, :businessActors, :BusinessActor
204
204
  relation :servedBy, :applicationServices, :ApplicationService
205
205
  relation :exposes, :applicationInterfaces, :ApplicationInterface
@@ -25,6 +25,13 @@ class Archsight::Resources::ComplianceEvidence < Archsight::Resources::Base
25
25
  - Test results and reports
26
26
  - Configuration documentation
27
27
  - Process documentation
28
+
29
+ ## Evidence of what
30
+
31
+ - **Of an application:** linked with `evidencedBy` from a service, component or technology element; it says how
32
+ that resource meets the requirement it `satisfies`
33
+ - **Of a control:** linked with `evidencedBy` from a `BusinessControl`; the records the control produces
34
+ (reviews, diagrams, change history, audit logs). Use `evidence/type` `process`, `documentation` or `audit-log`
28
35
  MD
29
36
 
30
37
  icon "shield-check"
@@ -40,5 +47,33 @@ class Archsight::Resources::ComplianceEvidence < Archsight::Resources::Base
40
47
  enum: %w[implemented partial not-implemented],
41
48
  summary: true
42
49
 
43
- relation :satisfies, :businessRequirements, :BusinessRequirement
50
+ # The structured answer to "how does the evidenced resource meet the requirement": one markdown field per
51
+ # question. `architecture/description` stays a short summary; the fields hold the detail.
52
+ annotation "evidence/mechanism",
53
+ description: "How the requirement is implemented: the concrete mechanism and where it lives " \
54
+ "(code, chart, configuration), as a short markdown list",
55
+ format: :markdown
56
+
57
+ annotation "evidence/coverage",
58
+ description: "What the mechanism covers and what it does not (data, flows, tenants, environments)",
59
+ format: :markdown
60
+
61
+ annotation "evidence/operatorView",
62
+ description: "Whether the mechanism holds against operators (admins, platform staff) or only against " \
63
+ "other tenants; names the privileged paths",
64
+ format: :markdown
65
+
66
+ annotation "evidence/verification",
67
+ description: "How effectiveness is verified: tests, audits, documents; says so when there are none",
68
+ format: :markdown
69
+
70
+ annotation "evidence/gaps",
71
+ description: "Remaining gaps and open questions, the most important first",
72
+ format: :markdown
73
+
74
+ annotation "evidence/sources",
75
+ description: "Where the statements come from: repositories and files, wiki pages, tickets",
76
+ format: :markdown
77
+
78
+ relation :satisfies, :motivationRequirements, :MotivationRequirement
44
79
  end
@@ -46,5 +46,5 @@ class Archsight::Resources::DataObject < Archsight::Resources::Base
46
46
  title: "Schema Variants",
47
47
  sidebar: false
48
48
 
49
- relation :realizes, :businessConstraints, :BusinessConstraint
49
+ relation :realizes, :motivationConstraints, :MotivationConstraint
50
50
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # BusinessConstraint represents restrictions or limitations on architecture
4
- class Archsight::Resources::BusinessConstraint < Archsight::Resources::Base
3
+ # MotivationConstraint represents restrictions or limitations on architecture
4
+ class Archsight::Resources::MotivationConstraint < Archsight::Resources::Base
5
5
  include_annotations :git, :architecture
6
6
 
7
7
  description <<~MD
@@ -18,7 +18,7 @@ class Archsight::Resources::BusinessConstraint < Archsight::Resources::Base
18
18
 
19
19
  ## Usage
20
20
 
21
- Use BusinessConstraint to represent:
21
+ Use MotivationConstraint to represent:
22
22
 
23
23
  - Regulatory requirements (GDPR, SOX, PCI-DSS)
24
24
  - Security policies
@@ -28,5 +28,5 @@ class Archsight::Resources::BusinessConstraint < Archsight::Resources::Base
28
28
  MD
29
29
 
30
30
  icon "prohibition"
31
- layer "business"
31
+ layer "motivation"
32
32
  end
@@ -33,5 +33,5 @@ class Archsight::Resources::MotivationGoal < Archsight::Resources::Base
33
33
 
34
34
  relation :realizes, :outcomes, :MotivationOutcome
35
35
  relation :refinedBy, :goals, :MotivationGoal
36
- relation :realizes, :businessRequirements, :BusinessRequirement
36
+ relation :realizes, :motivationRequirements, :MotivationRequirement
37
37
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # BusinessRequirement represents functional or non-functional requirements
4
- class Archsight::Resources::BusinessRequirement < Archsight::Resources::Base
3
+ # MotivationRequirement represents functional or non-functional requirements
4
+ class Archsight::Resources::MotivationRequirement < Archsight::Resources::Base
5
5
  include_annotations :git, :architecture
6
6
 
7
7
  description <<~MD
@@ -18,17 +18,27 @@ class Archsight::Resources::BusinessRequirement < Archsight::Resources::Base
18
18
 
19
19
  ## Usage
20
20
 
21
- Use BusinessRequirement to represent:
21
+ Use MotivationRequirement to represent:
22
22
 
23
23
  - Compliance requirements (C5, ISO 27001)
24
24
  - Security requirements
25
25
  - Performance requirements
26
26
  - Functional specifications
27
27
  - Legal obligations (GDPR, NIS2)
28
+
29
+ ## Who addresses it
30
+
31
+ A requirement is where the process side and the application side meet:
32
+
33
+ - **Process side:** a `BusinessControl` `satisfies` the requirement; a process is `guidedBy` the control
34
+ - **Application side:** applications `realize` or `plan` the requirement and are `evidencedBy` `ComplianceEvidence`,
35
+ which `satisfies` it
36
+
37
+ Both show as incoming relations on the requirement's page.
28
38
  MD
29
39
 
30
40
  icon "task-list"
31
- layer "business"
41
+ layer "motivation"
32
42
 
33
43
  annotation "requirement/type",
34
44
  description: "Type of requirement (business or legal)",
@@ -39,7 +49,7 @@ class Archsight::Resources::BusinessRequirement < Archsight::Resources::Base
39
49
  description: "Regulatory or standard reference (comma-separated for multiple)",
40
50
  filter: :list,
41
51
  enum: %w[c5-2020 itgs-2023 gdpr-2018 nis1 nis2 iso27001 sox pci-dss hipaa eu-data-act-2025 ens
42
- iso27001-2022],
52
+ iso27001-2022 vsa-2023 con-11-1],
43
53
  summary: true
44
54
 
45
55
  annotation "requirement/priority",
@@ -32,7 +32,7 @@ class Archsight::Resources::MotivationStakeholder < Archsight::Resources::Base
32
32
  layer "motivation"
33
33
 
34
34
  relation :hasConcern, :strategyCapabilities, :StrategyCapability
35
- relation :hasConcern, :businessRequirements, :BusinessRequirement
36
- relation :hasConcern, :businessConstraints, :BusinessConstraint
35
+ relation :hasConcern, :motivationRequirements, :MotivationRequirement
36
+ relation :hasConcern, :motivationConstraints, :MotivationConstraint
37
37
  relation :hasConcern, :goals, :MotivationGoal
38
38
  end
@@ -30,8 +30,8 @@ class Archsight::Resources::StrategyCapability < Archsight::Resources::Base
30
30
  icon "strategy"
31
31
  layer "strategy"
32
32
 
33
- relation :realizes, :businessConstraints, :BusinessConstraint
34
- relation :realizes, :businessRequirements, :BusinessRequirement
33
+ relation :realizes, :motivationConstraints, :MotivationConstraint
34
+ relation :realizes, :motivationRequirements, :MotivationRequirement
35
35
  relation :servedBy, :businessActors, :BusinessActor
36
36
  relation :servedBy, :applicationServices, :ApplicationService
37
37
  relation :servedBy, :businessProcesses, :BusinessProcess
@@ -36,7 +36,7 @@ class Archsight::Resources::TechnologyNode < Archsight::Resources::Base
36
36
  enum: %w[vm bare-metal kubernetes-node network-appliance storage-array],
37
37
  summary: true
38
38
 
39
- relation :realizes, :businessConstraints, :BusinessConstraint
39
+ relation :realizes, :motivationConstraints, :MotivationConstraint
40
40
  relation :servedBy, :technologyServices, :TechnologyService
41
41
  relation :servedBy, :businessActors, :BusinessActor
42
42
  end
@@ -37,8 +37,8 @@ class Archsight::Resources::TechnologyService < Archsight::Resources::Base
37
37
 
38
38
  relation :suppliedBy, :technologyComponents, :TechnologySystemSoftware
39
39
  relation :servedBy, :businessActors, :BusinessActor
40
- relation :realizes, :businessRequirements, :BusinessRequirement
41
- relation :partiallyRealizes, :businessRequirements, :BusinessRequirement
42
- relation :plans, :businessRequirements, :BusinessRequirement
40
+ relation :realizes, :motivationRequirements, :MotivationRequirement
41
+ relation :partiallyRealizes, :motivationRequirements, :MotivationRequirement
42
+ relation :plans, :motivationRequirements, :MotivationRequirement
43
43
  relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
44
44
  end