archsight 0.3.0 → 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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +1 -1
  3. data/README.md +7 -2
  4. data/docs/computed_annotations.md +7 -5
  5. data/docs/icons.md +3 -2
  6. data/docs/index.md.erb +5 -4
  7. data/docs/licenses.md +1 -2
  8. data/docs/modeling.md +127 -6
  9. data/docs/pages.md +72 -6
  10. data/docs/search.md +26 -2
  11. data/docs/togaf.md +4 -0
  12. data/lib/archsight/annotations/annotation.rb +5 -4
  13. data/lib/archsight/annotations/computed.rb +5 -1
  14. data/lib/archsight/annotations/relation_resolver.rb +109 -83
  15. data/lib/archsight/cli.rb +9 -2
  16. data/lib/archsight/database.rb +59 -4
  17. data/lib/archsight/diagram.rb +7 -0
  18. data/lib/archsight/documentation.rb +9 -5
  19. data/lib/archsight/editor.rb +2 -2
  20. data/lib/archsight/export/confluence/exporter.rb +11 -2
  21. data/lib/archsight/export/confluence/storage.rb +99 -8
  22. data/lib/archsight/export/confluence/tables.rb +78 -0
  23. data/lib/archsight/helpers/fenced_blocks.rb +61 -0
  24. data/lib/archsight/helpers/requirements_blocks.rb +90 -0
  25. data/lib/archsight/helpers/resource_resolver.rb +1 -1
  26. data/lib/archsight/helpers/view_blocks.rb +108 -0
  27. data/lib/archsight/helpers/wiki_links.rb +50 -5
  28. data/lib/archsight/helpers.rb +3 -0
  29. data/lib/archsight/import/handlers/go_grapher.rb +4 -1
  30. data/lib/archsight/import/handlers/go_module_parser.rb +4 -1
  31. data/lib/archsight/linter.rb +31 -3
  32. data/lib/archsight/mcp/analyze_resource_tool.rb +2 -2
  33. data/lib/archsight/mcp/base.rb +38 -0
  34. data/lib/archsight/mcp/resource_doc_tool.rb +2 -2
  35. data/lib/archsight/query/ast.rb +2 -1
  36. data/lib/archsight/query/evaluator.rb +2 -2
  37. data/lib/archsight/references.rb +129 -0
  38. data/lib/archsight/requirements.rb +70 -0
  39. data/lib/archsight/resources/analysis.rb +2 -1
  40. data/lib/archsight/resources/application_component.rb +8 -3
  41. data/lib/archsight/resources/application_interface.rb +5 -3
  42. data/lib/archsight/resources/application_service.rb +8 -7
  43. data/lib/archsight/resources/base.rb +67 -17
  44. data/lib/archsight/resources/business_actor.rb +5 -3
  45. data/lib/archsight/resources/business_control.rb +80 -0
  46. data/lib/archsight/resources/business_process.rb +3 -2
  47. data/lib/archsight/resources/business_product.rb +6 -5
  48. data/lib/archsight/resources/compliance_evidence.rb +40 -3
  49. data/lib/archsight/resources/data_object.rb +3 -2
  50. data/lib/archsight/resources/import.rb +5 -2
  51. data/lib/archsight/resources/{business_constraint.rb → motivation_constraint.rb} +4 -4
  52. data/lib/archsight/resources/motivation_goal.rb +1 -1
  53. data/lib/archsight/resources/{business_requirement.rb → motivation_requirement.rb} +19 -8
  54. data/lib/archsight/resources/motivation_stakeholder.rb +2 -2
  55. data/lib/archsight/resources/page.rb +4 -2
  56. data/lib/archsight/resources/strategy_capability.rb +2 -2
  57. data/lib/archsight/resources/technology_artifact.rb +4 -3
  58. data/lib/archsight/resources/technology_node.rb +2 -2
  59. data/lib/archsight/resources/technology_service.rb +4 -0
  60. data/lib/archsight/resources/technology_system_software.rb +4 -0
  61. data/lib/archsight/resources/view.rb +2 -1
  62. data/lib/archsight/resources.rb +34 -3
  63. data/lib/archsight/template.rb +2 -2
  64. data/lib/archsight/version.rb +1 -1
  65. data/lib/archsight/view_table.rb +102 -0
  66. data/lib/archsight/web/api/docs.rb +1 -1
  67. data/lib/archsight/web/api/json_helpers.rb +21 -15
  68. data/lib/archsight/web/api/openapi/spec.yaml +135 -2
  69. data/lib/archsight/web/api/page_helpers.rb +8 -7
  70. data/lib/archsight/web/api/requirements_helpers.rb +26 -0
  71. data/lib/archsight/web/api/routes.rb +19 -0
  72. data/lib/archsight/web/application.rb +12 -2
  73. data/lib/archsight/web/public/vue/ApiDocsPage-DZ0fa5-h.css +1 -0
  74. data/lib/archsight/web/public/vue/ApiDocsPage-DoOxKjG0.js +1 -0
  75. data/lib/archsight/web/public/vue/{DocPage-uaT8CdFm.js → DocPage-CfsC3CeQ.js} +1 -1
  76. data/lib/archsight/web/public/vue/EditorPage-CbG4mc9T.css +1 -0
  77. data/lib/archsight/web/public/vue/EditorPage-CsJA0q8n.js +35 -0
  78. data/lib/archsight/web/public/vue/ErrorPage-Ck9izEUS.css +1 -0
  79. data/lib/archsight/web/public/vue/ErrorPage-OJpJz9df.js +2 -0
  80. data/lib/archsight/web/public/vue/GraphView-BvWAbUAl.css +1 -0
  81. data/lib/archsight/web/public/vue/GraphView-CBq6oFRV.js +1 -0
  82. data/lib/archsight/web/public/vue/HomePage-BwgMLgVC.js +2 -0
  83. data/lib/archsight/web/public/vue/InstanceRouter-DQz-SsQq.js +1 -0
  84. data/lib/archsight/web/public/vue/InstanceRouter-jeavM6PW.css +1 -0
  85. data/lib/archsight/web/public/vue/KindList-4q0K9Cll.js +1 -0
  86. data/lib/archsight/web/public/vue/PageView-DzeGnvoS.js +1 -0
  87. data/lib/archsight/web/public/vue/QueryError-3EqjpbyV.css +1 -0
  88. data/lib/archsight/web/public/vue/QueryError-kg6Yf-pW.js +1 -0
  89. data/lib/archsight/web/public/vue/ResourceList-B0FLaFR3.css +1 -0
  90. data/lib/archsight/web/public/vue/ResourceList-Df_0e0qt.js +2 -0
  91. data/lib/archsight/web/public/vue/SearchResults-CKgY6_wH.js +1 -0
  92. data/lib/archsight/web/public/vue/SearchResults-CyPUZZSC.css +1 -0
  93. data/lib/archsight/web/public/vue/WikiPage-DoFJ0dbW.css +1 -0
  94. data/lib/archsight/web/public/vue/WikiPage-DrvCHwJ7.js +13 -0
  95. data/lib/archsight/web/public/vue/architecture-7GRP2DOG-BfA1TPxQ.js +1 -0
  96. data/lib/archsight/web/public/vue/cynefin-OW5HDTMX-BmgdUKVD.js +1 -0
  97. data/lib/archsight/web/public/vue/eventmodeling-NTZA5JFV-DbXDvAWF.js +1 -0
  98. data/lib/archsight/web/public/vue/gitGraph-4MIJSDKK-BkVhrKS1.js +1 -0
  99. data/lib/archsight/web/public/vue/index-D0Q5GZRs.js +3 -0
  100. data/lib/archsight/web/public/vue/index-oF-iyZvk.css +1 -0
  101. data/lib/archsight/web/public/vue/info-A6RAGUB7-BIlCVuUY.js +1 -0
  102. data/lib/archsight/web/public/vue/mermaid-BjEi5URd.js +3334 -0
  103. data/lib/archsight/web/public/vue/packet-AYTQ26CC-eoQTjY3j.js +1 -0
  104. data/lib/archsight/web/public/vue/pie-WAS4IAKB-DEOotGhh.js +1 -0
  105. data/lib/archsight/web/public/vue/radar-RG4KPBEZ-C2yucuUN.js +1 -0
  106. data/lib/archsight/web/public/vue/railroad-74A4TZTK-CzONK5VY.js +1 -0
  107. data/lib/archsight/web/public/vue/railroad-abnf-HS5TGJTU-9zRPsDsx.js +1 -0
  108. data/lib/archsight/web/public/vue/railroad-ebnf-LZEXJU2U-BJ4dkz9l.js +1 -0
  109. data/lib/archsight/web/public/vue/railroad-peg-WCYAUIDC-DFy_rqAj.js +1 -0
  110. data/lib/archsight/web/public/vue/treeView-Q6P3EWNA-2cq5CyD2.js +1 -0
  111. data/lib/archsight/web/public/vue/treemap-WGGIJYW6-DK-e9Ez4.js +1 -0
  112. data/lib/archsight/web/public/vue/{useGraphviz-C71SdG-N.js → useGraphviz-DweKV7Kg.js} +1 -1
  113. data/lib/archsight/web/public/vue/wardley-WFR3VGLG-BQwqUqfB.js +1 -0
  114. data/lib/archsight/web/public/vue.html +3 -3
  115. data/lib/archsight.rb +2 -0
  116. metadata +53 -42
  117. data/lib/archsight/web/public/vue/ApiDocsPage-C0y953v0.css +0 -1
  118. data/lib/archsight/web/public/vue/ApiDocsPage-C_4tAWis.js +0 -1
  119. data/lib/archsight/web/public/vue/EditorPage-C557BJC-.js +0 -35
  120. data/lib/archsight/web/public/vue/EditorPage-df5N-p21.css +0 -1
  121. data/lib/archsight/web/public/vue/ErrorPage-Vdebmife.js +0 -2
  122. data/lib/archsight/web/public/vue/ErrorPage-uMDnfY5_.css +0 -1
  123. data/lib/archsight/web/public/vue/GraphView-BduUql2N.js +0 -1
  124. data/lib/archsight/web/public/vue/GraphView-Cj2V2stN.css +0 -1
  125. data/lib/archsight/web/public/vue/HomePage-BHUTg8Ap.js +0 -2
  126. data/lib/archsight/web/public/vue/InstanceRouter-1lSigA58.js +0 -1
  127. data/lib/archsight/web/public/vue/InstanceRouter-Di7f3Rya.css +0 -1
  128. data/lib/archsight/web/public/vue/KindList-BDZs0j6d.js +0 -1
  129. data/lib/archsight/web/public/vue/PageView-BYzJDwof.js +0 -1
  130. data/lib/archsight/web/public/vue/ResourceList-CnbhU9wm.js +0 -1
  131. data/lib/archsight/web/public/vue/ResourceList-xyBwu7fh.css +0 -1
  132. data/lib/archsight/web/public/vue/SearchResults-CNhf6VOx.js +0 -1
  133. data/lib/archsight/web/public/vue/SearchResults-DOHzqAy3.css +0 -1
  134. data/lib/archsight/web/public/vue/WikiPage-DbmWkM7W.js +0 -13
  135. data/lib/archsight/web/public/vue/WikiPage-GV67QmNJ.css +0 -1
  136. data/lib/archsight/web/public/vue/architecture-TIHT7OUA-Bf-MWFmn.js +0 -1
  137. data/lib/archsight/web/public/vue/cynefin-VYW2F7L2-CkKt8qqs.js +0 -1
  138. data/lib/archsight/web/public/vue/eventmodeling-45OFAUF4-NwSTYPHj.js +0 -1
  139. data/lib/archsight/web/public/vue/gitGraph-TEB2WS4Q-DLRegrRG.js +0 -1
  140. data/lib/archsight/web/public/vue/index-CyVWObLU.js +0 -3
  141. data/lib/archsight/web/public/vue/index-DtKeHT3S.css +0 -1
  142. data/lib/archsight/web/public/vue/info-DKCQHKI2-EV39NzoN.js +0 -1
  143. data/lib/archsight/web/public/vue/mermaid-BMkGfnhm.js +0 -3279
  144. data/lib/archsight/web/public/vue/packet-7NZHBO7P-USljV3MZ.js +0 -1
  145. data/lib/archsight/web/public/vue/pie-RZYD4A2V-DChciBW2.js +0 -1
  146. data/lib/archsight/web/public/vue/radar-I7S5WNFK-CgGJdW1t.js +0 -1
  147. data/lib/archsight/web/public/vue/railroad-3IZDKUUU-Dy43vF5c.js +0 -1
  148. data/lib/archsight/web/public/vue/railroad-abnf-AHOZXSZD-BhsTH-sE.js +0 -1
  149. data/lib/archsight/web/public/vue/railroad-ebnf-EBAXGLYW-BBTd_u0u.js +0 -1
  150. data/lib/archsight/web/public/vue/railroad-peg-LSFZ7HO6-BiNTEzf0.js +0 -1
  151. data/lib/archsight/web/public/vue/treeView-QDETBFTQ-BfnCcf-q.js +0 -1
  152. data/lib/archsight/web/public/vue/treemap-6X3UGDF4-BsAORhvk.js +0 -1
  153. data/lib/archsight/web/public/vue/wardley-OPB4EBWU-DQvnEhRj.js +0 -1
@@ -36,7 +36,8 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
36
36
  sidebar: false,
37
37
  filter: :word,
38
38
  format: :tag_word,
39
- type: Archsight::Annotations::EmailRecipient
39
+ type: Archsight::Annotations::EmailRecipient,
40
+ summary: true
40
41
 
41
42
  annotation "team/members",
42
43
  description: 'Team members (format: "Name <email>" or "email")',
@@ -108,7 +109,7 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
108
109
  computed_annotation "team/size",
109
110
  title: "Team Size",
110
111
  description: "Number of team members (including sub-teams)",
111
- list: true,
112
+ summary: true,
112
113
  type: Integer do
113
114
  # Count members from this team
114
115
  members = @instance.annotations["team/members"]
@@ -147,7 +148,8 @@ class Archsight::Resources::BusinessActor < Archsight::Resources::Base
147
148
  computed_annotation "repository/artifacts/total",
148
149
  title: "Maintained Repositories",
149
150
  description: "Number of git repositories maintained by this team",
150
- type: Integer do
151
+ type: Integer,
152
+ summary: true do
151
153
  count(incoming_transitive('TechnologyArtifact: artifact/type == "repo"'))
152
154
  end
153
155
 
@@ -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
@@ -33,7 +33,8 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
33
33
  computed_annotation "repository/artifacts/total",
34
34
  title: "Total Git Repositories",
35
35
  description: "Number of related git repositories",
36
- type: Integer do
36
+ type: Integer,
37
+ summary: true do
37
38
  count(outgoing_transitive('TechnologyArtifact: artifact/type == "repo"'))
38
39
  end
39
40
 
@@ -110,7 +111,7 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
110
111
  computed_annotation "activity/createdAt",
111
112
  title: "Created",
112
113
  description: "Earliest repository creation date across all related application services",
113
- list: true,
114
+ summary: true,
114
115
  type: Time do
115
116
  services = outgoing_transitive(:ApplicationService)
116
117
  next nil if services.empty?
@@ -170,7 +171,7 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
170
171
  computed_annotation "activity/contributors/6m",
171
172
  title: "Contributors (6 months)",
172
173
  description: "Sum of unique contributors in the last 6 months across related services",
173
- list: true,
174
+ summary: true,
174
175
  type: Integer do
175
176
  services = outgoing_transitive(:ApplicationService)
176
177
  next nil if services.empty?
@@ -197,8 +198,8 @@ class Archsight::Resources::BusinessProduct < Archsight::Resources::Base
197
198
  end
198
199
 
199
200
  relation :realizes, :strategyCapabilities, :StrategyCapability
200
- relation :realizes, :businessConstraints, :BusinessConstraint
201
- relation :realizes, :businessRequirements, :BusinessRequirement
201
+ relation :realizes, :motivationConstraints, :MotivationConstraint
202
+ relation :realizes, :motivationRequirements, :MotivationRequirement
202
203
  relation :servedBy, :businessActors, :BusinessActor
203
204
  relation :servedBy, :applicationServices, :ApplicationService
204
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"
@@ -32,11 +39,41 @@ class Archsight::Resources::ComplianceEvidence < Archsight::Resources::Base
32
39
 
33
40
  annotation "evidence/type",
34
41
  description: "Type of evidence",
35
- enum: %w[documentation process configuration audit-log technical-control]
42
+ enum: %w[documentation process configuration audit-log technical-control],
43
+ summary: true
36
44
 
37
45
  annotation "evidence/status",
38
46
  description: "Current status of evidence",
39
- enum: %w[implemented partial not-implemented]
47
+ enum: %w[implemented partial not-implemented],
48
+ summary: true
49
+
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
40
77
 
41
- relation :satisfies, :businessRequirements, :BusinessRequirement
78
+ relation :satisfies, :motivationRequirements, :MotivationRequirement
42
79
  end
@@ -38,12 +38,13 @@ class Archsight::Resources::DataObject < Archsight::Resources::Base
38
38
  annotation "data/visibility",
39
39
  description: "API visibility level",
40
40
  title: "Visibility",
41
- enum: %w[public private internal]
41
+ enum: %w[public private internal],
42
+ summary: true
42
43
 
43
44
  annotation "generated/variants",
44
45
  description: "OpenAPI schema variants compacted into this DataObject",
45
46
  title: "Schema Variants",
46
47
  sidebar: false
47
48
 
48
- relation :realizes, :businessConstraints, :BusinessConstraint
49
+ relation :realizes, :motivationConstraints, :MotivationConstraint
49
50
  end
@@ -38,7 +38,9 @@ class Archsight::Resources::Import < Archsight::Resources::Base
38
38
  # Handler selection
39
39
  annotation "import/handler",
40
40
  description: "Handler class name to execute this import",
41
- title: "Handler", enum: %w[
41
+ title: "Handler",
42
+ summary: true,
43
+ enum: %w[
42
44
  gitlab github repository
43
45
  rest-api rest-api-index
44
46
  jira-discover jira-metrics
@@ -57,7 +59,8 @@ class Archsight::Resources::Import < Archsight::Resources::Base
57
59
  annotation "import/enabled",
58
60
  description: "Whether this import is enabled",
59
61
  title: "Enabled",
60
- enum: %w[true false]
62
+ enum: %w[true false],
63
+ summary: true
61
64
 
62
65
  annotation "import/priority",
63
66
  description: "Execution priority (lower runs first among ready imports)",
@@ -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,34 +18,45 @@ 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)",
35
- enum: %w[business legal compliance functional non-functional]
45
+ enum: %w[business legal compliance functional non-functional],
46
+ summary: true
36
47
 
37
48
  annotation "requirement/reference",
38
49
  description: "Regulatory or standard reference (comma-separated for multiple)",
39
50
  filter: :list,
40
51
  enum: %w[c5-2020 itgs-2023 gdpr-2018 nis1 nis2 iso27001 sox pci-dss hipaa eu-data-act-2025 ens
41
- iso27001-2022],
42
- list: true
52
+ iso27001-2022 vsa-2023 con-11-1],
53
+ summary: true
43
54
 
44
55
  annotation "requirement/priority",
45
56
  description: "Implementation priority (must, should, may)",
46
57
  filter: :word,
47
58
  enum: %w[must should may],
48
- list: true
59
+ summary: true
49
60
 
50
61
  annotation "requirement/story",
51
62
  description: "One-line business value statement explaining what the requirement enables",
@@ -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
@@ -38,11 +38,13 @@ class Archsight::Resources::Page < Archsight::Resources::Base
38
38
  description: "Owner responsible for keeping the page up to date (Name <email@domain.com>, or just a name)",
39
39
  title: "Owner",
40
40
  type: Archsight::Annotations::Person,
41
- sidebar: false
41
+ sidebar: false,
42
+ summary: true
42
43
  annotation "page/status",
43
44
  description: "Lifecycle status (e.g. rfc, wip, approved)",
44
45
  filter: :word,
45
- title: "Status"
46
+ title: "Status",
47
+ summary: true
46
48
  annotation "page/tags",
47
49
  description: "Comma-separated tags",
48
50
  filter: :list,
@@ -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
@@ -74,7 +74,7 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
74
74
  description: "Repository activity status",
75
75
  title: "Activity Status",
76
76
  enum: %w[active abandoned bot-only archived inaccessible empty no-code],
77
- list: true
77
+ summary: true
78
78
  annotation "activity/reason",
79
79
  description: "Reason for activity status (for non-standard statuses)",
80
80
  title: "Status Reason",
@@ -83,7 +83,7 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
83
83
  description: "Bus factor assessment",
84
84
  title: "Bus Factor",
85
85
  enum: %w[high medium low unknown],
86
- list: true
86
+ summary: true
87
87
  annotation "activity/createdAt",
88
88
  description: "Date of first commit (repository creation)",
89
89
  title: "Created",
@@ -130,7 +130,8 @@ class Archsight::Resources::TechnologyArtifact < Archsight::Resources::Base
130
130
  annotation "repository/visibility",
131
131
  description: "Repository visibility classification",
132
132
  title: "Visibility",
133
- enum: %w[private internal open-source public]
133
+ enum: %w[private internal open-source public],
134
+ summary: true
134
135
  annotation "repository/recentTags",
135
136
  description: "Recent git tags (releases)",
136
137
  title: "Recent Tags",
@@ -34,9 +34,9 @@ class Archsight::Resources::TechnologyNode < Archsight::Resources::Base
34
34
  description: "Type of infrastructure node",
35
35
  title: "Infrastructure Type",
36
36
  enum: %w[vm bare-metal kubernetes-node network-appliance storage-array],
37
- list: true
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,4 +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, :motivationRequirements, :MotivationRequirement
41
+ relation :partiallyRealizes, :motivationRequirements, :MotivationRequirement
42
+ relation :plans, :motivationRequirements, :MotivationRequirement
43
+ relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
40
44
  end
@@ -34,4 +34,8 @@ class Archsight::Resources::TechnologySystemSoftware < Archsight::Resources::Bas
34
34
  relation :realizedThrough, :technologyArtifacts, :TechnologyArtifact
35
35
  relation :exposes, :applicationInterfaces, :ApplicationInterface
36
36
  relation :dependsOn, :applicationInterfaces, :ApplicationInterface
37
+ relation :realizes, :motivationRequirements, :MotivationRequirement
38
+ relation :partiallyRealizes, :motivationRequirements, :MotivationRequirement
39
+ relation :plans, :motivationRequirements, :MotivationRequirement
40
+ relation :evidencedBy, :complianceEvidences, :ComplianceEvidence
37
41
  end
@@ -42,7 +42,8 @@ class Archsight::Resources::View < Archsight::Resources::Base
42
42
  description: "Display type for results",
43
43
  title: "Display Type",
44
44
  enum: %w[list:name list:name+kind],
45
- sidebar: false
45
+ sidebar: false,
46
+ summary: true
46
47
 
47
48
  annotation "view/sort",
48
49
  description: 'Comma-separated list of fields to sort by. Prefix with - for descending (e.g., "-scc/language/Go/loc,name"). Special fields: name, kind',
@@ -6,6 +6,37 @@ module Archsight
6
6
  # Store the class mapping
7
7
  @resource_classes = {}
8
8
 
9
+ # Kinds that were renamed: the old name is still accepted (see Database#create_valid_instance) and reported
10
+ # as deprecated by `archsight lint`. The table is only consulted by lookups; `each` and `resource_classes`
11
+ # list every kind once, under its new name. Remove an entry (and its relation keys below) to end the transition.
12
+ KIND_ALIASES = {
13
+ "BusinessRequirement" => "MotivationRequirement",
14
+ "BusinessConstraint" => "MotivationConstraint"
15
+ }.freeze
16
+
17
+ # Relation keys (the plural kind names under `spec.<verb>`) that were renamed together with their kind
18
+ RELATION_KEY_ALIASES = {
19
+ "businessRequirements" => "motivationRequirements",
20
+ "businessConstraints" => "motivationConstraints"
21
+ }.freeze
22
+
23
+ # Relations that are never written in a file: they are derived from what a resource's text and diagram say
24
+ # (see Archsight::References). `mentions` comes from links in text, `depicts` from the nodes of a diagram.
25
+ # Every kind can have them towards every kind, so instead of one relation per target kind a class gets one per
26
+ # verb, under the key DERIVED_KEY, that holds resources of any kind (relations are followed by looking at the
27
+ # resources they hold, not at the kind they were declared for).
28
+ DERIVED_VERBS = %i[mentions depicts].freeze
29
+ DERIVED_KEY = :resources
30
+
31
+ # The derived relations of every class, next to the ones it declares: [verb, key, kind]
32
+ DERIVED_RELATIONS = DERIVED_VERBS.map { |verb| [verb, DERIVED_KEY, "Resource"] }.freeze
33
+
34
+ # The current name of a kind, whichever of its names is given
35
+ def self.canonical(klass_name)
36
+ name = klass_name.to_s
37
+ KIND_ALIASES.fetch(name, name)
38
+ end
39
+
9
40
  # Register a resource class
10
41
  def self.register(klass)
11
42
  # Skip anonymous classes (used in tests)
@@ -20,9 +51,9 @@ module Archsight
20
51
  @resource_classes
21
52
  end
22
53
 
23
- # Returns the class by name
54
+ # Returns the class by name; a renamed kind is found under its old name too
24
55
  def self.[](klass_name)
25
- @resource_classes[klass_name.to_s]
56
+ @resource_classes[canonical(klass_name)]
26
57
  end
27
58
 
28
59
  # Iterate over all resource class names (sorted)
@@ -32,7 +63,7 @@ module Archsight
32
63
 
33
64
  # Get the constant by name (for backward compatibility with const_get)
34
65
  def self.const_get(name)
35
- @resource_classes[name.to_s] || super
66
+ @resource_classes[canonical(name)] || super
36
67
  end
37
68
  end
38
69
  end
@@ -35,10 +35,10 @@ module Archsight
35
35
  end
36
36
 
37
37
  def add_relations(yaml, klass)
38
- return if klass.relations.empty?
38
+ return if klass.declared_relations.empty?
39
39
 
40
40
  yaml["spec"] = {} if yaml["spec"].nil?
41
- klass.relations.each do |verb, relation_kind, _relation_klass|
41
+ klass.declared_relations.each do |verb, relation_kind, _relation_klass|
42
42
  relation_verb = verb.to_s.delete_prefix(":")
43
43
  yaml["spec"][relation_verb] ||= {}
44
44
  yaml["spec"][relation_verb][relation_kind.to_s] = []
@@ -4,5 +4,5 @@
4
4
  # Do not edit manually.
5
5
 
6
6
  module Archsight
7
- VERSION = "0.3.0"
7
+ VERSION = "0.3.2"
8
8
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Archsight
4
+ # The result of a view as a plain table (columns and rows of text), for places that cannot run the frontend, such
5
+ # as the Confluence export. It follows what the web UI shows for a View (`ViewResults.vue`, `ResourceList.vue`):
6
+ # a Name column, a Kind column unless the view is `list:name`, one column per `view/fields` entry that is not
7
+ # `name` or `kind`, rows sorted by `view/sort`.
8
+ module ViewTable
9
+ # Rows beyond this are cut (the table says how many); a Confluence page is no place for thousands of rows
10
+ LIMIT = 200
11
+
12
+ # @param as [Symbol] :text, :status or :priority (a requirement's, shown as a lozenge) or :markdown
13
+ # @param resources [Array] resources the cell names (shown one per line, linked where a target exists)
14
+ Cell = Struct.new(:text, :resources, :as, keyword_init: true) do
15
+ def initialize(text: "", resources: [], as: :text) = super
16
+ end
17
+
18
+ # @param title [String] shown above the table, may be empty
19
+ # @param columns [Array<String>] header texts
20
+ # @param rows [Array<Array<Cell>>] at most LIMIT
21
+ # @param total [Integer] number of rows before cutting
22
+ Table = Struct.new(:title, :columns, :rows, :total, keyword_init: true) do
23
+ def cut = total - rows.length
24
+ end
25
+
26
+ IDENTITY_FIELDS = %w[name kind].freeze
27
+
28
+ module_function
29
+
30
+ # @raise [Archsight::Query::QueryError] if the query does not parse
31
+ def build(database, query:, fields: [], sort: [], show_kind: true, title: "", limit: LIMIT)
32
+ resources = sort_resources(Archsight::Query.parse(query).filter(database), sort)
33
+ columns = fields.reject { |f| IDENTITY_FIELDS.include?(f) }
34
+ header = ["Name", (show_kind ? "Kind" : nil), *columns.map { |f| column_title(f) }].compact
35
+ rows = resources.first(limit).map do |resource|
36
+ [Cell.new(text: resource.name, resources: [resource]),
37
+ (Cell.new(text: kind_of(resource)) if show_kind),
38
+ *columns.map { |f| Cell.new(text: resource.annotations[f].to_s) }].compact
39
+ end
40
+ Table.new(title: title, columns: header, rows: rows, total: resources.length)
41
+ end
42
+
43
+ # The table of a ```view block (see Helpers::ViewBlocks)
44
+ # @raise [Helpers::ViewBlocks::Error, Archsight::Query::QueryError]
45
+ def from_block(database, source)
46
+ spec = Helpers::ViewBlocks.parse(source)
47
+ build(database, query: spec[:query], fields: spec[:fields], sort: spec[:sort], show_kind: spec[:type] == "list:name+kind", title: spec[:title])
48
+ end
49
+
50
+ # The table of a View resource
51
+ # @raise [Helpers::ViewBlocks::Error, Archsight::Query::QueryError]
52
+ def from_view(database, view)
53
+ annotations = view.annotations
54
+ query = annotations["view/query"].to_s.strip
55
+ raise Helpers::ViewBlocks::Error, "view #{view.name} has no query" if query.empty?
56
+
57
+ build(database, query: query, fields: list(annotations["view/fields"]), sort: list(annotations["view/sort"]),
58
+ show_kind: (annotations["view/type"] || "list:name+kind") == "list:name+kind", title: view.name)
59
+ end
60
+
61
+ # "scc/language/Go/loc" -> "Go loc", "activity/createdAt" -> "Activity created At" (as ResourceList.vue titles columns)
62
+ def column_title(key)
63
+ segments = key.split("/")
64
+ title = segments.length >= 2 ? "#{segments[-2]} #{segments[-1]}" : segments.last.to_s
65
+ title.gsub(/([a-z])([A-Z])/, '\1 \2').sub(/\A./, &:upcase)
66
+ end
67
+
68
+ def kind_of(resource) = resource.class.name.split("::").last
69
+
70
+ def list(value) = value.to_s.split(",").map(&:strip).reject(&:empty?)
71
+
72
+ # `view/sort` fields: name, kind or an annotation, "-" for descending; numbers inside text compare as numbers.
73
+ # Without a sort the order is by name, like the search API.
74
+ def sort_resources(resources, sort)
75
+ return resources.sort_by(&:name) if sort.empty?
76
+
77
+ resources.sort { |left, right| compare(left, right, sort) }
78
+ end
79
+
80
+ def compare(left, right, sort)
81
+ sort.each do |field|
82
+ key = field.delete_prefix("-")
83
+ order = natural(value_of(left, key)) <=> natural(value_of(right, key))
84
+ return field.start_with?("-") ? -order : order unless order.zero?
85
+ end
86
+ 0
87
+ end
88
+
89
+ def value_of(resource, key)
90
+ case key
91
+ when "name" then resource.name
92
+ when "kind" then kind_of(resource)
93
+ else resource.annotations[key].to_s
94
+ end
95
+ end
96
+
97
+ # A key for sorting text with numbers in it the way people expect (item2 < item10, case-insensitive first)
98
+ def natural(text)
99
+ [text.scan(/\d+|\D+/).map { |part| part.match?(/\A\d/) ? [0, part.to_i] : [1, part.downcase] }, text]
100
+ end
101
+ end
102
+ end