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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 464d36ecbae64b55e25554f9d60d9c208fb2477ba1910f2318c415eef2cea018
|
|
4
|
+
data.tar.gz: e423de1798a1a0fc99f16220051139800780e6bd9f01b096bce378dda1ccb25e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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, :
|
|
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
|
|
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 (``, ``); 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
|
-
|
|
|
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
|
|
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
|
|
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
|
-
- [
|
|
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
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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 `` 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:
|
|
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
|
-
###
|
|
174
|
+
### Requirements of a selection of resources
|
|
175
175
|
|
|
176
|
-
The "
|
|
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 "
|
|
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
|
-
~>
|
|
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
|
-
~>
|
|
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.
|
|
97
|
+
inst.class.declared_relations.flat_map { |verb, kind_name, _klass_name| inst.relations(verb, kind_name) }
|
|
87
98
|
else
|
|
88
|
-
(inst
|
|
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.
|
|
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
|
-
|
|
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) }
|
data/lib/archsight/database.rb
CHANGED
|
@@ -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
|
|
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.
|
|
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)
|
data/lib/archsight/diagram.rb
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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)
|
data/lib/archsight/editor.rb
CHANGED
|
@@ -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
|
|
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)}"
|