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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +1 -1
- data/README.md +6 -1
- data/docs/icons.md +3 -2
- data/docs/index.md.erb +5 -4
- data/docs/modeling.md +122 -5
- data/docs/pages.md +12 -3
- data/docs/search.md +9 -2
- data/docs/togaf.md +4 -0
- data/lib/archsight/annotations/relation_resolver.rb +15 -6
- data/lib/archsight/cli.rb +6 -0
- data/lib/archsight/database.rb +57 -3
- data/lib/archsight/diagram.rb +7 -0
- data/lib/archsight/documentation.rb +7 -4
- data/lib/archsight/editor.rb +2 -2
- data/lib/archsight/helpers/requirements_blocks.rb +1 -1
- data/lib/archsight/helpers/resource_resolver.rb +1 -1
- data/lib/archsight/helpers/wiki_links.rb +11 -0
- data/lib/archsight/linter.rb +6 -0
- data/lib/archsight/mcp/analyze_resource_tool.rb +2 -2
- data/lib/archsight/mcp/resource_doc_tool.rb +2 -2
- data/lib/archsight/query/ast.rb +2 -1
- data/lib/archsight/query/evaluator.rb +2 -2
- data/lib/archsight/references.rb +129 -0
- data/lib/archsight/requirements.rb +4 -4
- data/lib/archsight/resources/application_component.rb +3 -0
- data/lib/archsight/resources/application_interface.rb +1 -1
- data/lib/archsight/resources/application_service.rb +4 -4
- data/lib/archsight/resources/base.rb +40 -5
- data/lib/archsight/resources/business_control.rb +80 -0
- data/lib/archsight/resources/business_process.rb +3 -2
- data/lib/archsight/resources/business_product.rb +2 -2
- data/lib/archsight/resources/compliance_evidence.rb +36 -1
- data/lib/archsight/resources/data_object.rb +1 -1
- data/lib/archsight/resources/{business_constraint.rb → motivation_constraint.rb} +4 -4
- data/lib/archsight/resources/motivation_goal.rb +1 -1
- data/lib/archsight/resources/{business_requirement.rb → motivation_requirement.rb} +15 -5
- data/lib/archsight/resources/motivation_stakeholder.rb +2 -2
- data/lib/archsight/resources/strategy_capability.rb +2 -2
- data/lib/archsight/resources/technology_node.rb +1 -1
- data/lib/archsight/resources/technology_service.rb +3 -3
- data/lib/archsight/resources/technology_system_software.rb +3 -3
- data/lib/archsight/resources.rb +34 -3
- data/lib/archsight/template.rb +2 -2
- data/lib/archsight/version.rb +1 -1
- data/lib/archsight/web/api/docs.rb +1 -1
- data/lib/archsight/web/api/json_helpers.rb +14 -10
- data/lib/archsight/web/api/openapi/spec.yaml +2 -2
- data/lib/archsight/web/api/page_helpers.rb +8 -7
- data/lib/archsight/web/api/routes.rb +1 -1
- data/lib/archsight/web/application.rb +6 -1
- data/lib/archsight/web/public/vue/{ApiDocsPage-D-cPRZCT.js → ApiDocsPage-DoOxKjG0.js} +1 -1
- data/lib/archsight/web/public/vue/{DocPage-DK6vNDbF.js → DocPage-CfsC3CeQ.js} +1 -1
- data/lib/archsight/web/public/vue/{EditorPage-BoJpQaVw.js → EditorPage-CsJA0q8n.js} +1 -1
- data/lib/archsight/web/public/vue/{ErrorPage-PGyjdtEf.js → ErrorPage-OJpJz9df.js} +1 -1
- data/lib/archsight/web/public/vue/{GraphView-BLiKR4zP.js → GraphView-CBq6oFRV.js} +1 -1
- data/lib/archsight/web/public/vue/HomePage-BwgMLgVC.js +2 -0
- data/lib/archsight/web/public/vue/InstanceRouter-DQz-SsQq.js +1 -0
- data/lib/archsight/web/public/vue/{InstanceRouter-60Tt3ZNM.css → InstanceRouter-jeavM6PW.css} +1 -1
- data/lib/archsight/web/public/vue/KindList-4q0K9Cll.js +1 -0
- data/lib/archsight/web/public/vue/{PageView-9MgHtrgl.js → PageView-DzeGnvoS.js} +1 -1
- data/lib/archsight/web/public/vue/{QueryError-D1FL1xgA.js → QueryError-kg6Yf-pW.js} +1 -1
- data/lib/archsight/web/public/vue/ResourceList-Df_0e0qt.js +2 -0
- data/lib/archsight/web/public/vue/SearchResults-CKgY6_wH.js +1 -0
- data/lib/archsight/web/public/vue/{SearchResults-DiW5XVYW.css → SearchResults-CyPUZZSC.css} +1 -1
- data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +1 -0
- data/lib/archsight/web/public/vue/{WikiPage-CeCQBTDS.js → WikiPage-DrvCHwJ7.js} +3 -3
- data/lib/archsight/web/public/vue/{index-D7m61Ahx.js → index-D0Q5GZRs.js} +2 -2
- data/lib/archsight/web/public/vue/index-oF-iyZvk.css +1 -0
- data/lib/archsight/web/public/vue.html +2 -2
- metadata +23 -21
- data/lib/archsight/web/public/vue/HomePage-C0lR8i2C.js +0 -2
- data/lib/archsight/web/public/vue/InstanceRouter-D3W2jJHV.js +0 -1
- data/lib/archsight/web/public/vue/KindList-BlsaRBNO.js +0 -1
- data/lib/archsight/web/public/vue/ResourceList-vkgFyeOY.js +0 -2
- data/lib/archsight/web/public/vue/SearchResults-Cl3O_OEr.js +0 -1
- data/lib/archsight/web/public/vue/WikiPage-C-8SG67a.css +0 -1
- data/lib/archsight/web/public/vue/index-Dbx3MXWG.css +0 -1
data/lib/archsight/linter.rb
CHANGED
|
@@ -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,
|
|
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
|
-
"
|
|
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
|
-
•
|
|
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.
|
|
64
|
+
relation_count: klass.declared_relations.count
|
|
65
65
|
}
|
|
66
66
|
end
|
|
67
67
|
|
data/lib/archsight/query/ast.rb
CHANGED
|
@@ -218,7 +218,8 @@ module Archsight::Query::AST
|
|
|
218
218
|
attr_reader :kind_name
|
|
219
219
|
|
|
220
220
|
def initialize(kind_name)
|
|
221
|
-
|
|
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
|
|
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
|
|
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 ``, 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
|
|
5
|
-
# shows them, merged over all resources of the selection: one entry per
|
|
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 = :
|
|
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? ? "
|
|
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, :
|
|
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, :
|
|
218
|
-
relation :realizes, :
|
|
219
|
-
relation :partiallyRealizes, :
|
|
220
|
-
relation :plans, :
|
|
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
|
-
@
|
|
19
|
-
@
|
|
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.
|
|
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.
|
|
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, :
|
|
34
|
-
relation :realizes, :
|
|
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, :
|
|
202
|
-
relation :realizes, :
|
|
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
|
-
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
class Archsight::Resources::
|
|
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
|
|
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 "
|
|
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, :
|
|
36
|
+
relation :realizes, :motivationRequirements, :MotivationRequirement
|
|
37
37
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
class Archsight::Resources::
|
|
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
|
|
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 "
|
|
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, :
|
|
36
|
-
relation :hasConcern, :
|
|
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, :
|
|
34
|
-
relation :realizes, :
|
|
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, :
|
|
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, :
|
|
41
|
-
relation :partiallyRealizes, :
|
|
42
|
-
relation :plans, :
|
|
40
|
+
relation :realizes, :motivationRequirements, :MotivationRequirement
|
|
41
|
+
relation :partiallyRealizes, :motivationRequirements, :MotivationRequirement
|
|
42
|
+
relation :plans, :motivationRequirements, :MotivationRequirement
|
|
43
43
|
relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
|
|
44
44
|
end
|