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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 336946fbac9548285ad368125db862c2f1926d99f2f40c1d40ba4155ebe8ca45
4
- data.tar.gz: 63ff3e1a973432f98b5ffb66ac9c518a204c0f87d90ee495615cf60bba293cd3
3
+ metadata.gz: 464d36ecbae64b55e25554f9d60d9c208fb2477ba1910f2318c415eef2cea018
4
+ data.tar.gz: e423de1798a1a0fc99f16220051139800780e6bd9f01b096bce378dda1ccb25e
5
5
  SHA512:
6
- metadata.gz: e76456b0f0ed4db22057a4df669953dfcdf7747ed8293ca3092be866e4eea6a4e4c63df6ffeaf6f163da552bf163f409dcb1184a1a493d12e565b0304e527202
7
- data.tar.gz: 60a37bef6e039f2bcb20d23fc1010baacdebc65cdded4e8d2f91992583bf60f8245c74175ef26945f3b67e67d6a3421de962c3d8513536376383b55e98bf8afb
6
+ metadata.gz: 0eb109e3f462b8ec1a23a17ab8c95d5d8bc176a5b54d038e47fa9dcf383e2597c9cc4a540efb1bd876714b279486dfd5a74d1bf1adc8c435a64db07913128890
7
+ data.tar.gz: 7ce31438036e3addcc8bda4e3587165f13b31399bd7a416323747d1552d9ae910c3e53191f8f7689f7b01a3d84508b9a32cf6cf7486fbb9bde3c0920560283bd
data/CONTRIBUTING.md CHANGED
@@ -84,7 +84,7 @@ class Archsight::Resources::MyResource < Archsight::Resources::Base
84
84
  description: 'Custom field description',
85
85
  enum: ['value1', 'value2']
86
86
 
87
- relation :realizes, :businessRequirements, :BusinessRequirement
87
+ relation :realizes, :motivationRequirements, :MotivationRequirement
88
88
  end
89
89
  ```
90
90
 
data/README.md CHANGED
@@ -91,6 +91,11 @@ archsight web
91
91
  claude mcp add --transport sse ionos-architecture http://localhost:4567/mcp/sse
92
92
  ```
93
93
 
94
+ For a deployed server use its address, for example `claude mcp add --transport sse archsight https://archsight.example.com/mcp/sse`.
95
+ The MCP endpoint accepts any hostname, like the web UI and the API, and has no authentication of its own: if the
96
+ ingress requires a token, pass it with `--header "Authorization: Bearer <token>"`. The ingress must not buffer or
97
+ time out the long-lived SSE connection.
98
+
94
99
  **Available tools:**
95
100
 
96
101
  - `query` - Search and filter resources using the query language
@@ -101,7 +106,7 @@ claude mcp add --transport sse ionos-architecture http://localhost:4567/mcp/sse
101
106
 
102
107
  **Macros** such as `{status:yellow WIP}` and `{emoticon:2705}` work inline in pages ([Wiki pages](docs/pages.md#macros)).
103
108
 
104
- **Views and analyses** can be embedded in pages with `![[View/Name]]` / `![[Analysis/Name]]` ([Wiki pages](docs/pages.md#embedding-views-and-analyses)); a view can also be written in place with a ```` ```view ```` block ([inline views](docs/pages.md#inline-views)), and the business requirements of a selection of resources shown with a ```` ```requirements ```` block ([requirements](docs/pages.md#business-requirements-of-a-selection-of-resources)).
109
+ **Views and analyses** can be embedded in pages with `![[View/Name]]` / `![[Analysis/Name]]` ([Wiki pages](docs/pages.md#embedding-views-and-analyses)); a view can also be written in place with a ```` ```view ```` block ([inline views](docs/pages.md#inline-views)), and the requirements of a selection of resources shown with a ```` ```requirements ```` block ([requirements](docs/pages.md#requirements-of-a-selection-of-resources)).
105
110
 
106
111
  **Images and draw.io diagrams** are plain files in the resources directory and are embedded in markdown with relative
107
112
  paths (`![](../img/a.png)`, `![](../../fop/flow.drawio)`); only files of image, draw.io and `.asd` diagram types inside the resources
data/docs/icons.md CHANGED
@@ -11,16 +11,17 @@ Current icon assignments for each resource type:
11
11
  | ApplicationInterface | `usb` | <i class="iconoir-usb"></i> |
12
12
  | ApplicationService | `cube` | <i class="iconoir-cube"></i> |
13
13
  | BusinessActor | `community` | <i class="iconoir-community"></i> |
14
- | BusinessConstraint | `prohibition` | <i class="iconoir-prohibition"></i> |
14
+ | BusinessControl | `shield-search` | <i class="iconoir-shield-search"></i> |
15
15
  | BusinessProcess | `kanban-board` | <i class="iconoir-kanban-board"></i> |
16
16
  | BusinessProduct | `box-iso` | <i class="iconoir-box-iso"></i> |
17
- | BusinessRequirement | `task-list` | <i class="iconoir-task-list"></i> |
18
17
  | ComplianceEvidence | `shield-check` | <i class="iconoir-shield-check"></i> |
19
18
  | DataObject | `database` | <i class="iconoir-database"></i> |
20
19
  | ApplicationComponent | `component` | <i class="iconoir-component"></i> |
21
20
  | TechnologySystemSoftware | `terminal-tag` | <i class="iconoir-terminal-tag"></i> |
22
21
  | MotivationGoal | `archery` | <i class="iconoir-archery"></i> |
23
22
  | MotivationOutcome | `badge-check` | <i class="iconoir-badge-check"></i> |
23
+ | MotivationRequirement | `task-list` | <i class="iconoir-task-list"></i> |
24
+ | MotivationConstraint | `prohibition` | <i class="iconoir-prohibition"></i> |
24
25
  | MotivationStakeholder | `user-crown` | <i class="iconoir-user-crown"></i> |
25
26
  | TechnologyNode | `server-connection` | <i class="iconoir-server-connection"></i> |
26
27
  | StrategyCapability | `strategy` | <i class="iconoir-strategy"></i> |
data/docs/index.md.erb CHANGED
@@ -24,21 +24,22 @@ The following diagram shows all resource types organized by layer and their rela
24
24
 
25
25
  ### Motivation Layer
26
26
 
27
- Resources representing stakeholders, goals, and outcomes that drive architectural decisions.
27
+ Resources representing stakeholders, goals, outcomes, requirements, and constraints that drive architectural decisions.
28
28
 
29
29
  - [MotivationStakeholder](/doc/resources/motivation_stakeholder) - Roles with interests in the architecture
30
30
  - [MotivationGoal](/doc/resources/motivation_goal) - High-level statements of intent
31
31
  - [MotivationOutcome](/doc/resources/motivation_outcome) - End results or consequences
32
+ - [MotivationRequirement](/doc/resources/motivation_requirement) - Compliance, legal and functional requirements
33
+ - [MotivationConstraint](/doc/resources/motivation_constraint) - Limitations and restrictions
32
34
 
33
35
  ### Business Layer
34
36
 
35
- Resources representing business processes, actors, and products.
37
+ Resources representing business processes, actors, products, and the controls that guide processes.
36
38
 
37
39
  - [BusinessActor](/doc/resources/business_actor) - Teams and organizations
38
40
  - [BusinessProcess](/doc/resources/business_process) - Structured business workflows
39
41
  - [BusinessProduct](/doc/resources/business_product) - Products offered to customers
40
- - [BusinessRequirement](/doc/resources/business_requirement) - Business and compliance requirements
41
- - [BusinessConstraint](/doc/resources/business_constraint) - Limitations and restrictions
42
+ - [BusinessControl](/doc/resources/business_control) - Controls that guide business processes
42
43
 
43
44
  ### Strategy Layer
44
45
 
data/docs/modeling.md CHANGED
@@ -52,6 +52,8 @@ Model **why** the architecture exists.
52
52
  | MotivationStakeholder | For roles that have interest in architecture outcomes (CTO, Security Team, Customers) |
53
53
  | MotivationGoal | For high-level objectives ("Achieve SOC 2 compliance", "Reduce latency") |
54
54
  | MotivationOutcome | For measurable results ("99.9% availability", "Sub-100ms response") |
55
+ | MotivationRequirement | For must-have capabilities (compliance, functional needs) |
56
+ | MotivationConstraint | For limitations (budget, regulations, technical debt) |
55
57
 
56
58
  **Example chain:** Stakeholder "Security Team" → hasConcern → Goal "Achieve Compliance" → realizes → Requirement "Encrypt data at rest"
57
59
 
@@ -64,11 +66,12 @@ Model **who** does **what** in business terms.
64
66
  | BusinessActor | For teams, departments, or organizations |
65
67
  | BusinessProcess | For workflows that produce business value |
66
68
  | BusinessProduct | For offerings to customers (cloud services, APIs) |
67
- | BusinessRequirement | For must-have capabilities (compliance, functional needs) |
68
- | BusinessConstraint | For limitations (budget, regulations, technical debt) |
69
+ | BusinessControl | For controls that guide a process (access review, change approval), with an owner and executors |
69
70
 
70
71
  **Example chain:** Actor "Platform Team" → performedBy → Process "Incident Response" → servedBy → Service "Monitoring"
71
72
 
73
+ **Controls:** Process "Incident Response" → guidedBy → Control "Escalation Review" → ownedBy / executedBy → Actor "Platform Team". How controls, requirements and evidence fit together is described under Relation Patterns below.
74
+
72
75
  ### Strategy Layer
73
76
 
74
77
  Model strategic **capabilities**.
@@ -114,7 +117,7 @@ Model **infrastructure** and **code**.
114
117
  Shows how abstract concepts become concrete:
115
118
 
116
119
  ```
117
- BusinessRequirement
120
+ MotivationRequirement
118
121
  ↓ realizes
119
122
  ApplicationService
120
123
  ↓ realizedThrough
@@ -135,12 +138,63 @@ TechnologyService
135
138
  TechnologySystemSoftware
136
139
  ```
137
140
 
141
+ ### Requirements, Controls and Evidence
142
+
143
+ A requirement is the point where the process side and the application side of compliance meet:
144
+
145
+ ```
146
+ Process side Application side
147
+
148
+ BusinessProcess ApplicationService / ApplicationComponent
149
+ ↓ guidedBy ↓ realizes / plans
150
+ BusinessControl ↓ evidencedBy
151
+ ↓ satisfies ComplianceEvidence
152
+ ↓ ↓ satisfies
153
+ └────────────→ MotivationRequirement ←────────┘
154
+ ```
155
+
156
+ | Kind | Answers | Key relations |
157
+ |------|---------|---------------|
158
+ | MotivationRequirement | What must hold? | satisfied by controls and evidence; realized or planned by applications |
159
+ | BusinessControl | What do we do about it, who does it, how often? | a process is `guidedBy` it; `ownedBy` and `executedBy` actors; `satisfies` requirements; `evidencedBy` evidence |
160
+ | ComplianceEvidence | How is it met, how can it be shown? | `satisfies` requirements; `evidencedBy` from an application, a technology element or a control |
161
+
162
+ How to model it:
163
+
164
+ - **Process side.** Model a control once, let every process it governs point to it with `guidedBy`, and link it with `satisfies` to each requirement it addresses. Give it an owner (the accountable actor) and executors (the actors carrying it out). The `control/status` and `control/frequency` filters find, for example, the controls that are only partially implemented.
165
+ - **Application side.** Whether an application implements a requirement is stated on the application (`realizes`, `plans`, `evidencedBy`), never on the control. Evidence is per resource and requirement: it says how *this* service or component meets *that* requirement.
166
+ - **Evidence of a control.** A control can be `evidencedBy` evidence too. Use it for the records the control itself produces (reviews, diagrams, change history, audit logs) and set `evidence/type` to `process`, `documentation` or `audit-log`. Evidence that an application meets a requirement is linked from the application, not from a control.
167
+ - **Reading it back.** A requirement's page lists its controls and evidence as incoming `satisfies` relations. The "Requirements" table of an application page and the `requirements` blocks list what applications implement (`realizes`, `partiallyRealizes`, `plans`); controls are not part of that table.
168
+
169
+ ```yaml
170
+ # process side
171
+ kind: BusinessProcess
172
+ metadata: { name: Process:ChangeManagement }
173
+ spec:
174
+ guidedBy:
175
+ businessControls: [Control:ChangeApproval]
176
+ ---
177
+ kind: BusinessControl
178
+ metadata: { name: Control:ChangeApproval }
179
+ spec:
180
+ ownedBy: { businessActors: [Team:Management] }
181
+ executedBy: { businessActors: [Team:Operations] }
182
+ satisfies: { motivationRequirements: [Requirement:ChangeTraceability] }
183
+ ---
184
+ # application side
185
+ kind: ApplicationService
186
+ metadata: { name: Deployment }
187
+ spec:
188
+ realizes: { motivationRequirements: [Requirement:ChangeTraceability] }
189
+ evidencedBy: { complianceEvidences: [Evidence:DeploymentAuditLog] }
190
+ ```
191
+
138
192
  ### Compliance Chain
139
193
 
140
194
  Shows how requirements are satisfied:
141
195
 
142
196
  ```
143
- BusinessRequirement
197
+ MotivationRequirement
144
198
  ↑ satisfies
145
199
  ComplianceEvidence
146
200
  ↑ evidencedBy
@@ -151,6 +205,44 @@ Technology elements such as a Kubernetes cluster runtime can plan, realize and b
151
205
  for requirements directly, so the requirement does not have to be attached to a placeholder
152
206
  ApplicationService. Applications deployed on them point to the TechnologyService with `servedBy`.
153
207
 
208
+ ApplicationComponents can be evidenced as well (`realizes` / `plans motivationRequirements`, `evidencedBy
209
+ complianceEvidences`), so requirements can be answered per component and not only per service.
210
+
211
+ A ComplianceEvidence answers "how is the requirement met" in structured markdown fields next to
212
+ `architecture/description` (kept as a short summary): `evidence/mechanism`, `evidence/coverage`,
213
+ `evidence/operatorView` (does it hold against operators or only against other tenants),
214
+ `evidence/verification`, `evidence/gaps` and `evidence/sources`. Because they are separate annotations they can be
215
+ queried, for example `ComplianceEvidence: evidence/gaps =~ "needs review"`.
216
+
217
+ ### Derived References
218
+
219
+ Text and diagrams already say which resources they are about, so archsight turns that into relations instead of asking you
220
+ to repeat it in YAML. There are two, and they are never written in a file:
221
+
222
+ | Relation | Comes from | Where |
223
+ |----------|------------|-------|
224
+ | `mentions` | `[[Target]]`, `[[Kind/Name]]` links and `![[View/Name]]` embeds | the content of a page, the description of any resource, any markdown annotation |
225
+ | `depicts` | the `resource "..."` of the nodes of a diagram | ```` ```asd ```` blocks and `![](file.asd)` files in those texts, and the `architecture/diagram` of any resource |
226
+
227
+ ```
228
+ Page "Architecture overview" --mentions--> ApplicationComponent "Core:Database" ([[Core:Database]])
229
+ Page "Architecture overview" --depicts---> ApplicationComponent "Web:API" (resource "Web:API" in a diagram)
230
+ ```
231
+
232
+ They are rebuilt on every load, so they cannot drift from the text. They show on both sides: a resource lists the pages
233
+ that mention and depict it under Relations ("Page / mentions"), and a page lists what it refers to. Only an exact name counts: a
234
+ page by its name or title, `Kind/Name`, or a resource name that exists once. Part of a name, a name that exists in several
235
+ kinds, and an unknown name do not make a relation (`archsight lint` reports unknown links in pages). Links in code are shown as
236
+ written and do not count, and neither does a resource referring to itself. Views and requirements blocks pick resources with a
237
+ query when they run, so they name nothing.
238
+
239
+ Derived relations are followed like written ones: queries (`ApplicationComponent: <- Page`, `Page: -> ApplicationComponent`),
240
+ transitive queries and impact analysis. A page that mentions a component therefore shows up when you ask what a change to the
241
+ component affects, which tells you which pages to update, and a resource that only a page mentions is not an orphan
242
+ (`<- none`). To leave them out of a query, exclude the verbs: `ApplicationComponent: ~{!mentions,depicts}> TechnologyArtifact`.
243
+ Computed annotations (costs, teams, repositories) follow written relations only, so a link in a text never changes a number.
244
+ They are not part of the editor's relation form, and `mentions:` or `depicts:` in a file is an unknown verb.
245
+
154
246
  ## Annotation Best Practices
155
247
 
156
248
  Use annotations to capture metadata:
@@ -194,7 +286,7 @@ relations:
194
286
  ### Compliance Mapping
195
287
 
196
288
  ```yaml
197
- kind: BusinessRequirement
289
+ kind: MotivationRequirement
198
290
  name: DataEncryption
199
291
  annotations:
200
292
  requirement/reference: c5-2020, gdpr-2018
@@ -204,3 +296,28 @@ relations:
204
296
  outcomes:
205
297
  - DataProtection
206
298
  ```
299
+
300
+ ## Renamed Kinds
301
+
302
+ Requirements and constraints are Motivation elements in ArchiMate, so their kinds are named that way:
303
+
304
+ | Old | New |
305
+ |-----|-----|
306
+ | `BusinessRequirement` | `MotivationRequirement` |
307
+ | `BusinessConstraint` | `MotivationConstraint` |
308
+ | relation key `businessRequirements` | `motivationRequirements` |
309
+ | relation key `businessConstraints` | `motivationConstraints` |
310
+
311
+ Both kinds moved from the Business to the Motivation layer. Their annotations (`requirement/*`) are unchanged.
312
+
313
+ For now the old names keep working: files with `kind: BusinessRequirement` or `businessRequirements:` keys load as they are, queries such as `BusinessRequirement: requirement/priority == "must"` and `~> BusinessRequirement` still match, and old links to `/kinds/BusinessRequirement/...` still open. Everything shown (the UI, the API, files written by the inline editor) uses the new names.
314
+
315
+ `archsight lint` lists every use of an old name as a deprecation, with the file and line, without failing. Replace them, for example with a find and replace over your resources directory (YAML files and the queries in markdown pages):
316
+
317
+ ```bash
318
+ find resources \( -name '*.yaml' -o -name '*.md' \) -exec sed -i.bak \
319
+ -e 's/BusinessRequirement/MotivationRequirement/g' -e 's/BusinessConstraint/MotivationConstraint/g' \
320
+ -e 's/businessRequirements/motivationRequirements/g' -e 's/businessConstraints/motivationConstraints/g' {} +
321
+ ```
322
+
323
+ The old names will be removed in a future release.
data/docs/pages.md CHANGED
@@ -171,14 +171,14 @@ It loads and runs in the browser like any embed; the server only writes `<div cl
171
171
  data-fields data-sort data-type>` around the source. A block that is not a valid View (broken YAML, missing or
172
172
  unparsable query, unknown `view/*` key or type) shows an error box with the source, and `archsight lint` reports it.
173
173
 
174
- ### Business requirements of a selection of resources
174
+ ### Requirements of a selection of resources
175
175
 
176
- The "Business Requirements" table of an instance page (the requirements it `realizes`, `partiallyRealizes` or `plans`,
176
+ The "Requirements" table of an instance page (the requirements it `realizes`, `partiallyRealizes` or `plans`,
177
177
  with status, priority and story) can be put on a page for any selection of resources with a ```` ```requirements ```` block:
178
178
 
179
179
  ````markdown
180
180
  ```requirements
181
- title: Requirements of the backup services # optional, default "Business Requirements"
181
+ title: Requirements of the backup services # optional, default "Requirements"
182
182
  of: 'ApplicationService: name =~ "Backup"' # required: query selecting the resources
183
183
  priority: must # optional: must, should, may (one value or a list)
184
184
  status: [implemented, partial] # optional: implemented, partial, planned
@@ -193,6 +193,15 @@ data-status>` around the source): the frontend loads the rows from `GET /api/v1/
193
193
  never runs the query. A block with a missing or unparsable `of`, an unknown key, priority or status shows an error box with the
194
194
  source, and `archsight lint` reports it.
195
195
 
196
+ ## What a page refers to
197
+
198
+ What a page says about the architecture becomes relations without anything to write: `[[Core:Database]]` or `[[Kind/Name]]` in the
199
+ text makes the page `mention` that resource, and a resource in a diagram of the page (an ```` ```asd ```` block or an embedded
200
+ `.asd` file) makes the page `depict` it. The resource then lists the page under Relations, and the page lists the resource, so you
201
+ can see which pages talk about a component, and which pages to review when it changes (impact analysis includes them). Only exact
202
+ names count and links in code are ignored. The same applies to the description and the diagram of every other resource. See
203
+ [Derived references](modeling.md#derived-references).
204
+
196
205
  ## Macros
197
206
 
198
207
  Inline macros are written `{name:arguments}` and named like the macros of Confluence. They work inside a sentence, a
data/docs/search.md CHANGED
@@ -121,7 +121,7 @@ Examples:
121
121
  -> ApplicationInterface # exposes an interface
122
122
  -> "Kubernetes:RestAPI" # exposes specific interface
123
123
  <- ApplicationComponent # referenced by a component
124
- ~> BusinessRequirement # transitively reaches requirement
124
+ ~> MotivationRequirement # transitively reaches requirement
125
125
  -> none & <- none # orphan (no relations)
126
126
  TechnologyArtifact: <- none # unreferenced artifacts
127
127
 
@@ -175,6 +175,13 @@ Examples:
175
175
  # Find artifacts with NO maintainedBy relations
176
176
  TechnologyArtifact: -{maintainedBy}> none
177
177
 
178
+ # Pages that mention a component, and the components they depict
179
+ ApplicationComponent: <{mentions}- Page
180
+ Page: -{depicts}> ApplicationComponent
181
+
182
+ # Leave the derived relations (links in text, nodes of diagrams) out of a traversal
183
+ ApplicationComponent: ~{!mentions,depicts}> TechnologyArtifact
184
+
178
185
  ## Sub-Query Targets
179
186
 
180
187
  Use `$(expression)` to dynamically find relation targets based on a query:
@@ -249,7 +256,7 @@ Large Go codebases:
249
256
 
250
257
  Resources with compliance chain:
251
258
 
252
- ~> BusinessRequirement
259
+ ~> MotivationRequirement
253
260
 
254
261
  Complex query with grouping:
255
262
 
data/docs/togaf.md CHANGED
@@ -124,6 +124,10 @@ The metamodel defines architectural entities and their relationships across all
124
124
 
125
125
  - **Principle**, **Constraint**, **Requirement**, **Gap**, **Work Package**, **Location**
126
126
 
127
+ **Governance:**
128
+
129
+ - **Control**: a decision-making step with accountability and authority, applied to a process or function. ArchiMate has no element for it; Archsight models it as `BusinessControl`, which a `BusinessProcess` is `guidedBy`
130
+
127
131
  ### Critical Relationships
128
132
 
129
133
  1. **Traceability**: Drivers → Goals → Objectives → Course of Action → Business Elements → Applications → Technology
@@ -11,6 +11,17 @@
11
11
  # - Symbol: Simple kind filter (e.g., :TechnologyArtifact)
12
12
  # - String: Query selector (e.g., 'TechnologyArtifact: activity/status == "active"')
13
13
  class Archsight::Annotations::ComputedRelationResolver
14
+ # The instances that refer to a resource through relations that are written in files. Computed annotations sum up the
15
+ # modelled architecture (costs, teams, repositories): what a page or a description merely mentions or depicts (see
16
+ # Archsight::References) must not change them, so they follow modelled relations only.
17
+ def self.modelled_references(inst)
18
+ (inst.references || []).filter_map do |ref|
19
+ next ref unless ref.is_a?(Hash)
20
+
21
+ ref[:instance] unless Archsight::Resources::DERIVED_VERBS.include?(ref[:verb])
22
+ end
23
+ end
24
+
14
25
  MAX_DEPTH = 10
15
26
 
16
27
  # TraversalCache holds what can be shared between all resolvers of one computation run: the unfiltered
@@ -83,9 +94,9 @@ class Archsight::Annotations::ComputedRelationResolver
83
94
 
84
95
  def neighbours(inst, direction)
85
96
  if direction == :outgoing
86
- inst.class.relations.flat_map { |verb, kind_name, _klass_name| inst.relations(verb, kind_name) }
97
+ inst.class.declared_relations.flat_map { |verb, kind_name, _klass_name| inst.relations(verb, kind_name) }
87
98
  else
88
- (inst.references || []).filter_map { |ref| ref.is_a?(Hash) ? ref[:instance] : ref }
99
+ Archsight::Annotations::ComputedRelationResolver.modelled_references(inst)
89
100
  end
90
101
  end
91
102
  end
@@ -103,7 +114,7 @@ class Archsight::Annotations::ComputedRelationResolver
103
114
  def outgoing(filter = nil)
104
115
  results = []
105
116
 
106
- @instance.class.relations.each do |_verb, kind_name, _klass_name|
117
+ @instance.class.declared_relations.each do |_verb, kind_name, _klass_name|
107
118
  rels = @instance.relations(_verb, kind_name)
108
119
  rels.each do |rel|
109
120
  results << rel if matches_filter?(rel, filter)
@@ -127,9 +138,7 @@ class Archsight::Annotations::ComputedRelationResolver
127
138
  # @param filter [Symbol, String, nil] Optional kind filter (Symbol) or query selector (String)
128
139
  # @return [Array] Array of instances that reference this one
129
140
  def incoming(filter = nil)
130
- refs = @instance.references || []
131
- # Extract instances from reference hashes
132
- instances = refs.map { |ref| ref.is_a?(Hash) ? ref[:instance] : ref }.compact
141
+ instances = self.class.modelled_references(@instance).compact
133
142
 
134
143
  if filter.nil?
135
144
  instances
data/lib/archsight/cli.rb CHANGED
@@ -127,6 +127,12 @@ module Archsight
127
127
  linter = Archsight::Linter.new(db)
128
128
  errors = linter.validate
129
129
 
130
+ warnings = linter.warnings
131
+ if warnings.any?
132
+ puts "Deprecations (#{warnings.count}):"
133
+ warnings.each { |warning| puts " #{warning}" }
134
+ end
135
+
130
136
  if errors.any?
131
137
  puts "Validation Errors (#{errors.count}):"
132
138
  errors.each { |error| display_error_with_context(error) }
@@ -5,6 +5,7 @@ require_relative "graph"
5
5
  require_relative "resources"
6
6
  require_relative "page_loader"
7
7
  require_relative "query"
8
+ require_relative "references"
8
9
 
9
10
  module Archsight
10
11
  # LineReference combines a path and line reference
@@ -40,12 +41,19 @@ module Archsight
40
41
  end
41
42
  end
42
43
 
44
+ # Deprecation is a use of a renamed kind or relation key that was accepted under its old name
45
+ Deprecation = Struct.new(:ref, :message) do
46
+ def to_s
47
+ "#{ref}: #{message}"
48
+ end
49
+ end
50
+
43
51
  # Database loads yaml files and folders to create an in-memory representation
44
52
  # of the structure. The loading and parsing of files will raise errors
45
53
  # if invalid data is passed.
46
54
  class Database
47
55
  attr_accessor :instances, :verbose, :verify, :compute_annotations, :only_kinds
48
- attr_reader :path
56
+ attr_reader :path, :deprecations
49
57
 
50
58
  def initialize(path, verbose: false, verify: true, compute_annotations: true, only_kinds: nil)
51
59
  @path = path
@@ -54,10 +62,12 @@ module Archsight
54
62
  @compute_annotations = compute_annotations
55
63
  @only_kinds = only_kinds
56
64
  @instances = {}
65
+ @deprecations = []
57
66
  end
58
67
 
59
68
  def reload!
60
69
  @instances = {}
70
+ @deprecations = []
61
71
 
62
72
  # load all resources
63
73
  Dir.glob(File.join(@path, "**/*.{yaml,md}")).each do |path|
@@ -67,6 +77,7 @@ module Archsight
67
77
  end
68
78
 
69
79
  verify! if @verify
80
+ derive_references! if @verify
70
81
  compute_all_annotations! if @verify && @compute_annotations
71
82
  rescue Psych::SyntaxError => e
72
83
  # Wrap YAML syntax errors in ResourceError for consistent handling
@@ -118,12 +129,49 @@ module Archsight
118
129
 
119
130
  kind = obj["kind"] || raise("kind not defined")
120
131
  klass = Archsight::Resources[kind] || raise("#{kind} is not a valid kind")
132
+ accept_old_names(obj)
121
133
  inst = klass.new(obj, @current_ref)
122
134
  raise("metadata name of #{kind} not present") if inst.name.to_s.strip.empty?
123
135
 
124
136
  inst
125
137
  end
126
138
 
139
+ # Documents may still use the old name of a renamed kind and the old relation keys. They are rewritten to the
140
+ # current names here, so everything after loading only sees those, and each rewrite is kept as a deprecation
141
+ # (reported by `archsight lint`).
142
+ def accept_old_names(obj)
143
+ kind = obj["kind"]
144
+ current = Archsight::Resources.canonical(kind)
145
+ if current != kind
146
+ deprecate("kind '#{kind}' is renamed to '#{current}'")
147
+ obj["kind"] = current
148
+ end
149
+
150
+ return unless obj["spec"].is_a?(Hash)
151
+
152
+ obj["spec"].each do |verb, keys|
153
+ next unless keys.is_a?(Hash)
154
+
155
+ Archsight::Resources::RELATION_KEY_ALIASES.each do |old_key, new_key|
156
+ next unless keys.key?(old_key)
157
+
158
+ deprecate("relation key '#{old_key}' under '#{verb}' is renamed to '#{new_key}'")
159
+ keys[new_key] = (Array(keys[new_key]) + Array(keys.delete(old_key))).uniq
160
+ end
161
+ end
162
+ end
163
+
164
+ def deprecate(message)
165
+ @deprecations << Deprecation.new(@current_ref, "#{message} (the old name will be removed in a future release)")
166
+ end
167
+
168
+ # Whether a document of this kind is wanted by the only_kinds filter, whichever of its names either side uses
169
+ def kind_wanted?(kind)
170
+ return true unless @only_kinds
171
+
172
+ @only_kinds.map { |k| Archsight::Resources.canonical(k) }.include?(Archsight::Resources.canonical(kind))
173
+ end
174
+
127
175
  def load_file(path)
128
176
  File.open(path, "r") do |f|
129
177
  YAML.parse_stream(f) do |node|
@@ -132,7 +180,7 @@ module Archsight
132
180
  next unless obj # skip empty / unknown documents
133
181
 
134
182
  # Skip resources that don't match only_kinds filter
135
- next if @only_kinds && !@only_kinds.include?(obj["kind"])
183
+ next unless kind_wanted?(obj["kind"])
136
184
 
137
185
  self << create_valid_instance(obj)
138
186
  end
@@ -186,7 +234,7 @@ module Archsight
186
234
  end
187
235
 
188
236
  def verify_instance_relations!(inst)
189
- inst.class.relations.each do |verb, kind, klass_name|
237
+ inst.class.declared_relations.each do |verb, kind, klass_name|
190
238
  rels = inst.relations(verb, kind).map do |rel_name|
191
239
  rel_klass = Archsight::Resources[klass_name] || raise_for(inst, "#{klass_name} is not a valid relation kind")
192
240
  kind_display = rel_klass.to_s.sub(/^Archsight::Resources::/, "")
@@ -197,6 +245,12 @@ module Archsight
197
245
  end
198
246
  end
199
247
 
248
+ # Adds the relations that the text and the diagrams of the resources imply (`mentions`, `depicts`), after all
249
+ # written relations are resolved and before the computed annotations are calculated, which follow relations.
250
+ def derive_references!
251
+ Archsight::References.derive!(self)
252
+ end
253
+
200
254
  # Compute all computed annotations for all instances
201
255
  def compute_all_annotations!
202
256
  manager = Archsight::Annotations::ComputedManager.new(self)
@@ -44,6 +44,13 @@ module Archsight
44
44
  time(profile, :render) { Renderer.render(graph, layout, relation_filter: relation_filter, style: style, id_prefix: id_prefix) }
45
45
  end
46
46
 
47
+ # The `resource "..."` references of the nodes of a diagram source, in order and without duplicates. Parses and
48
+ # builds the graph but does not lay it out, so it is cheap enough to run over every diagram of a model.
49
+ # @raise [Archsight::Diagram::Error] if the source is not a valid diagram
50
+ def self.resource_references(source)
51
+ Graph.build(Parser.parse(source)).nodes_by_id.values.filter_map { |node| node.attrs["resource"] }.uniq
52
+ end
53
+
47
54
  def self.time(profile, key)
48
55
  return yield unless profile
49
56
 
@@ -104,7 +104,7 @@ module Archsight
104
104
  # Skip kinds not in a displayed layer
105
105
  next unless LAYER_ORDER.include?(klass.layer)
106
106
 
107
- klass.relations.each do |verb, _relation_kind, target_klass|
107
+ klass.declared_relations.each do |verb, _relation_kind, target_klass|
108
108
  # Skip relations to excluded kinds
109
109
  next if EXCLUDED_KINDS.include?(target_klass.to_s)
110
110
 
@@ -149,13 +149,16 @@ module Archsight
149
149
  end
150
150
 
151
151
  def generate_relations_table(klass)
152
- return "_No relations defined._" if klass.relations.empty?
152
+ derived = "Every resource also has the derived relations #{Archsight::Resources::DERIVED_VERBS.map { |v| "`#{v}`" }.join(" and ")} " \
153
+ "towards any kind: they follow from links in its text and from its diagrams, so they are not written in files " \
154
+ "(see the Modeling Guide)."
155
+ return "_No relations defined._\n\n#{derived}" if klass.declared_relations.empty?
153
156
 
154
157
  rows = ["| Relation | Target | Kind |", "|----------|--------|------|"]
155
- klass.relations.each do |verb, kind, target_klass|
158
+ klass.declared_relations.each do |verb, kind, target_klass|
156
159
  rows << "| #{verb} | #{target_klass} | #{kind} |"
157
160
  end
158
- rows.join("\n")
161
+ "#{rows.join("\n")}\n\n#{derived}"
159
162
  end
160
163
 
161
164
  def format_values(annotation)
@@ -21,7 +21,7 @@ module Archsight
21
21
  def build_resource(kind:, name:, annotations: {}, relations: [])
22
22
  resource = {
23
23
  "apiVersion" => "architecture/v1alpha1",
24
- "kind" => kind,
24
+ "kind" => Archsight::Resources.canonical(kind), # saving a resource that had the old name migrates it
25
25
  "metadata" => {
26
26
  "name" => name
27
27
  }
@@ -129,7 +129,7 @@ module Archsight
129
129
  klass = Archsight::Resources[kind]
130
130
  return [] unless klass
131
131
 
132
- klass.relations
132
+ klass.declared_relations # derived relations are not written in files
133
133
  end
134
134
 
135
135
  # Get unique verbs for a resource kind's relations
@@ -5,7 +5,7 @@ require_relative "fenced_blocks"
5
5
 
6
6
  module Archsight
7
7
  module Helpers
8
- # Turns ```requirements fenced blocks in rendered markdown into the table of business requirements of a
8
+ # Turns ```requirements fenced blocks in rendered markdown into the table of requirements of a
9
9
  # selection of resources (see Archsight::Requirements):
10
10
  #
11
11
  # ```requirements
@@ -38,7 +38,7 @@ module Archsight
38
38
  klass = Archsight::Resources[kind]
39
39
  return [] unless klass && @database.instances.fetch(klass, {}).key?(name)
40
40
 
41
- [[kind, name]]
41
+ [[Archsight::Resources.canonical(kind), name]] # a renamed kind is linked under its new name
42
42
  end
43
43
 
44
44
  def in_any_kind(name)
@@ -64,6 +64,17 @@ module Archsight
64
64
  found.is_a?(Array) ? @database.instances_by_kind(found.first)[found.last] : nil
65
65
  end
66
66
 
67
+ # Like target_for, but only an exact name counts: a page by name or title, `Kind/Name`, or a resource name that
68
+ # exists in one kind. A target that only matches part of a name does not name anything. For relations that are
69
+ # derived from text, where a wrong guess would be a wrong relation.
70
+ def exact_target_for(target)
71
+ page = find_page(target)
72
+ return page if page
73
+
74
+ found = @resolver.find(target)
75
+ found.is_a?(Array) ? @database.instances_by_kind(found.first)[found.last] : nil
76
+ end
77
+
67
78
  # Page path for a page name, nil if there is no such page
68
79
  def page_path(page)
69
80
  "/pages/#{ERB::Util.url_encode(page.name)}"