archgraph-argo 0.26.2 → 0.28.0
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.
- package/README.md +127 -57
- package/argo/.env.example +181 -163
- package/argo/plugins/argo-wakeup.js +29 -27
- package/argo/rules/archgraph.instructions.md +189 -187
- package/argo/schema/schema-bundle.config.json +22 -0
- package/argo/schema/schema-bundle.rules.json +12229 -0
- package/argo/scripts/agentSearchDiagnose.js +410 -407
- package/argo/scripts/argo-mcp-server.js +122 -9
- package/argo/scripts/ensureArgoHarnessEnvironment.js +14 -0
- package/argo/scripts/external-graph-query.js +162 -0
- package/argo/scripts/graph-rag/defaultSemanticRetrieval.js +0 -13
- package/argo/scripts/graph-rag/liveEmbeddingProviderConfig.js +9 -61
- package/argo/scripts/graph-semantics.js +363 -227
- package/argo/scripts/runArchitectureTests.js +591 -583
- package/argo/scripts/schema-bundle.js +932 -0
- package/argo/scripts/systemarchitecture-mcp-server.js +758 -110
- package/argo/scripts/validateSystemArchitecture.js +262 -253
- package/argo/scripts/validator-mcp-server.js +1 -1
- package/argo/scripts/workspace-write-guard.js +144 -0
- package/argo/skills/argo-init/SKILL.md +7 -9
- package/argo/skills/ea-human-reconcile/SKILL.md +40 -39
- package/cordis.patch.yml +15 -6
- package/dsh-argo-wakeup/index.js +17 -17
- package/dsh-argo-workspace/index.js +227 -141
- package/install-argo.ps1 +336 -206
- package/package.json +57 -53
|
@@ -1,227 +1,363 @@
|
|
|
1
|
-
// Shared graph-semantics validation for SystemArchitecture.
|
|
2
|
-
// Used by both validateSystemArchitecture.js (full validation) and
|
|
3
|
-
// systemarchitecture-mcp-server.js (mutation-path validation).
|
|
4
|
-
//
|
|
5
|
-
// This module eliminates the duplicate validateGraphSemantics implementations.
|
|
6
|
-
// All callers get identical core checks;
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} = require('./
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
errors.push(`views '${view.view_id}'
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
for (const relationship of
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* @param {
|
|
194
|
-
*
|
|
195
|
-
* @param {object} [options]
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
1
|
+
// Shared graph-semantics validation for SystemArchitecture.
|
|
2
|
+
// Used by both validateSystemArchitecture.js (full validation) and
|
|
3
|
+
// systemarchitecture-mcp-server.js (mutation-path validation).
|
|
4
|
+
//
|
|
5
|
+
// This module eliminates the duplicate validateGraphSemantics implementations.
|
|
6
|
+
// All callers get identical core checks; the modeling language (element types,
|
|
7
|
+
// relationship types, endpoint matrix, statement grammar, root-view name and
|
|
8
|
+
// view element limit) is supplied by an *ontology* so a repository that ships
|
|
9
|
+
// its own schema under .argo/schema is validated against its own language.
|
|
10
|
+
//
|
|
11
|
+
// The ontology argument is optional: when omitted, the default ArchiMate 3.2
|
|
12
|
+
// (ArchiMate 3.2 + ARGO) ontology is used, preserving historical behaviour.
|
|
13
|
+
|
|
14
|
+
const { loadSchemaBundleAndOntology } = require('./schema-bundle.js');
|
|
15
|
+
|
|
16
|
+
let defaultOntology = null;
|
|
17
|
+
|
|
18
|
+
function resolveOntology(ontology) {
|
|
19
|
+
if (ontology) {
|
|
20
|
+
return ontology;
|
|
21
|
+
}
|
|
22
|
+
if (!defaultOntology) {
|
|
23
|
+
defaultOntology = loadSchemaBundleAndOntology(process.cwd()).ontology;
|
|
24
|
+
}
|
|
25
|
+
return defaultOntology;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Core graph-semantics checks that are always identical for all callers.
|
|
30
|
+
*
|
|
31
|
+
* @param {object} document - parsed SystemArchitecture JSON
|
|
32
|
+
* @param {string[]} errors - error accumulator
|
|
33
|
+
* @param {object} [ontology] - resolved modeling language (defaults to ArchiMate 3.2)
|
|
34
|
+
*/
|
|
35
|
+
function validateGraphSemantics(document, errors, ontology) {
|
|
36
|
+
if (!document || typeof document !== 'object') {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const language = resolveOntology(ontology);
|
|
40
|
+
const invariants = language.invariants || {};
|
|
41
|
+
const elementTypeLabel = language.elementTypeErrorLabel || language.language || 'the';
|
|
42
|
+
const relationshipTypeLabel = language.relationshipTypeErrorLabel || language.language || 'the';
|
|
43
|
+
const rootViewName = invariants.rootViewName === undefined ? 'SystemArchitecture' : invariants.rootViewName;
|
|
44
|
+
|
|
45
|
+
const elements = Array.isArray(document.elements) ? document.elements : [];
|
|
46
|
+
const relationships = Array.isArray(document.relationships) ? document.relationships : [];
|
|
47
|
+
const views = Array.isArray(document.views) ? document.views : [];
|
|
48
|
+
const elementById = new Map();
|
|
49
|
+
const relationshipById = new Map();
|
|
50
|
+
|
|
51
|
+
// --- elements: identity, type, parent ---
|
|
52
|
+
for (const element of elements) {
|
|
53
|
+
if (!element || typeof element !== 'object') {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (elementById.has(element.id)) {
|
|
57
|
+
errors.push(`elements contains duplicate id '${element.id}'`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
elementById.set(element.id, element);
|
|
61
|
+
if (!language.isSupportedElementType(element.type)) {
|
|
62
|
+
errors.push(`elements '${element.id}' uses unsupported ${elementTypeLabel} element type '${element.type}'`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const element of elements) {
|
|
67
|
+
if (!element || typeof element !== 'object' || !element.parent) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!elementById.has(element.parent)) {
|
|
71
|
+
errors.push(`elements '${element.id}' references missing parent '${element.parent}'`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- relationships: identity, type, endpoints, statement ---
|
|
76
|
+
for (const relationship of relationships) {
|
|
77
|
+
if (!relationship || typeof relationship !== 'object') {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (relationshipById.has(relationship.id)) {
|
|
81
|
+
errors.push(`relationships contains duplicate id '${relationship.id}'`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
relationshipById.set(relationship.id, relationship);
|
|
85
|
+
if (!language.isSupportedRelationshipType(relationship.type)) {
|
|
86
|
+
errors.push(`relationships '${relationship.id}' uses unsupported ${relationshipTypeLabel} relationship type '${relationship.type}'`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const source = elementById.get(relationship.source_id);
|
|
90
|
+
if (!source) {
|
|
91
|
+
errors.push(`relationships '${relationship.id}' references missing source_id '${relationship.source_id}'`);
|
|
92
|
+
} else if (relationship.source_name !== source.name) {
|
|
93
|
+
errors.push(`relationships '${relationship.id}' source_name '${relationship.source_name}' does not match element '${relationship.source_id}' name '${source.name}'`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const target = elementById.get(relationship.target_id);
|
|
97
|
+
if (!target) {
|
|
98
|
+
errors.push(`relationships '${relationship.id}' references missing target_id '${relationship.target_id}'`);
|
|
99
|
+
} else if (relationship.target_name !== target.name) {
|
|
100
|
+
errors.push(`relationships '${relationship.id}' target_name '${relationship.target_name}' does not match element '${relationship.target_id}' name '${target.name}'`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (invariants.statementGrammar === false) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const expectedStatement = source && target
|
|
107
|
+
? `${source.name} --(${relationship.type})--> ${target.name}`
|
|
108
|
+
: undefined;
|
|
109
|
+
if (expectedStatement && relationship.statement !== expectedStatement) {
|
|
110
|
+
errors.push(`relationships '${relationship.id}' statement must be '${expectedStatement}'`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- views: topology, membership, endpoint co-occurrence ---
|
|
115
|
+
const topLevelViews = views.filter(view => view && typeof view === 'object' && !view.parent_element_id);
|
|
116
|
+
if (topLevelViews.length !== 1) {
|
|
117
|
+
errors.push(
|
|
118
|
+
rootViewName
|
|
119
|
+
? `views must contain exactly one top-level view named '${rootViewName}'; found ${topLevelViews.length}`
|
|
120
|
+
: `views must contain exactly one top-level view; found ${topLevelViews.length}`,
|
|
121
|
+
);
|
|
122
|
+
} else if (rootViewName && topLevelViews[0].view_name !== rootViewName) {
|
|
123
|
+
errors.push(`top-level view '${topLevelViews[0].view_id}' view_name must be '${rootViewName}'`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const elementIdsIncludedInViews = new Set();
|
|
127
|
+
const relationshipIdsIncludedInViews = new Set();
|
|
128
|
+
for (const view of views) {
|
|
129
|
+
if (!view || typeof view !== 'object') {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (!view.parent_element_id && rootViewName && view.view_name !== rootViewName) {
|
|
133
|
+
errors.push(`views '${view.view_id}' must declare parent_element_id unless it is the top-level ${rootViewName} view`);
|
|
134
|
+
}
|
|
135
|
+
if (view.parent_element_id) {
|
|
136
|
+
const parent = elementById.get(view.parent_element_id);
|
|
137
|
+
if (!parent) {
|
|
138
|
+
errors.push(`views '${view.view_id}' references missing parent_element_id '${view.parent_element_id}'`);
|
|
139
|
+
} else if (view.parent_element_name && view.parent_element_name !== parent.name) {
|
|
140
|
+
errors.push(`views '${view.view_id}' parent_element_name '${view.parent_element_name}' does not match element '${view.parent_element_id}' name '${parent.name}'`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const includedElementIds = new Set(view.included_elements || []);
|
|
144
|
+
if (includedElementIds.size !== (view.included_elements || []).length) {
|
|
145
|
+
errors.push(`views '${view.view_id}' must not contain duplicate included_elements`);
|
|
146
|
+
}
|
|
147
|
+
for (const elementId of view.included_elements || []) {
|
|
148
|
+
elementIdsIncludedInViews.add(elementId);
|
|
149
|
+
if (!elementById.has(elementId)) {
|
|
150
|
+
errors.push(`views '${view.view_id}' references missing included element '${elementId}'`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const includedRelationshipIds = new Set(view.included_relationships || []);
|
|
154
|
+
if (includedRelationshipIds.size !== (view.included_relationships || []).length) {
|
|
155
|
+
errors.push(`views '${view.view_id}' must not contain duplicate included_relationships`);
|
|
156
|
+
}
|
|
157
|
+
for (const relationshipId of view.included_relationships || []) {
|
|
158
|
+
relationshipIdsIncludedInViews.add(relationshipId);
|
|
159
|
+
const rel = relationshipById.get(relationshipId);
|
|
160
|
+
if (!rel) {
|
|
161
|
+
errors.push(`views '${view.view_id}' references missing included relationship '${relationshipId}'`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!includedElementIds.has(rel.source_id)) {
|
|
165
|
+
errors.push(`views '${view.view_id}' includes relationship '${relationshipId}' but not source element '${rel.source_id}'`);
|
|
166
|
+
}
|
|
167
|
+
if (!includedElementIds.has(rel.target_id)) {
|
|
168
|
+
errors.push(`views '${view.view_id}' includes relationship '${relationshipId}' but not target element '${rel.target_id}'`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
for (const element of elements) {
|
|
174
|
+
if (element && typeof element === 'object' && !elementIdsIncludedInViews.has(element.id)) {
|
|
175
|
+
errors.push(`elements '${element.id}' must be included in at least one view`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const relationship of relationships) {
|
|
180
|
+
if (relationship && typeof relationship === 'object' && !relationshipIdsIncludedInViews.has(relationship.id)) {
|
|
181
|
+
errors.push(`relationships '${relationship.id}' must be included in at least one view`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Validate the modeling language's endpoint-type matrix for relationships.
|
|
188
|
+
* No-op when the ontology disables the endpoint matrix invariant.
|
|
189
|
+
*
|
|
190
|
+
* @param {object} document - parsed SystemArchitecture JSON
|
|
191
|
+
* @param {string[]} errors - error accumulator
|
|
192
|
+
* @param {object} [options]
|
|
193
|
+
* @param {string[]} [options.touchedRelationshipIds] - if provided, only these
|
|
194
|
+
* relationships are checked; if omitted/empty, ALL relationships are checked
|
|
195
|
+
* @param {object} [options.ontology] - resolved modeling language
|
|
196
|
+
*/
|
|
197
|
+
function validateArchiMateEndpointMatrix(document, errors, options = {}) {
|
|
198
|
+
const ontology = resolveOntology(options.ontology);
|
|
199
|
+
if (ontology.invariants && ontology.invariants.endpointMatrix === false) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const elementById = new Map(
|
|
203
|
+
(document.elements || []).map(element => [element.id, element]),
|
|
204
|
+
);
|
|
205
|
+
const relationshipIdSet =
|
|
206
|
+
Array.isArray(options.touchedRelationshipIds) && options.touchedRelationshipIds.length > 0
|
|
207
|
+
? new Set(options.touchedRelationshipIds)
|
|
208
|
+
: undefined;
|
|
209
|
+
|
|
210
|
+
for (const relationship of document.relationships || []) {
|
|
211
|
+
if (relationshipIdSet && !relationshipIdSet.has(relationship.id)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const source = elementById.get(relationship.source_id);
|
|
215
|
+
const target = elementById.get(relationship.target_id);
|
|
216
|
+
errors.push(...ontology.validateRelationshipEndpointTypes(relationship, source, target));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Validate the ontology's per-view element limit (default 15).
|
|
222
|
+
* `maxElementsPerView: null` disables the limit. included_relationships do not
|
|
223
|
+
* consume the quota.
|
|
224
|
+
*
|
|
225
|
+
* @param {object} document - parsed SystemArchitecture JSON
|
|
226
|
+
* @param {string[]} errors - error accumulator
|
|
227
|
+
* @param {object} [options]
|
|
228
|
+
* @param {string[]} [options.touchedViewIds] - if provided, only these views
|
|
229
|
+
* are checked; if omitted/empty, ALL views are checked
|
|
230
|
+
* @param {object} [options.ontology] - resolved modeling language
|
|
231
|
+
*/
|
|
232
|
+
function validateViewElementLimits(document, errors, options = {}) {
|
|
233
|
+
const ontology = resolveOntology(options.ontology);
|
|
234
|
+
const maxIncludedElements = ontology.invariants && ontology.invariants.maxElementsPerView !== undefined
|
|
235
|
+
? ontology.invariants.maxElementsPerView
|
|
236
|
+
: 15;
|
|
237
|
+
if (maxIncludedElements === null) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const touchedViewIdSet =
|
|
241
|
+
Array.isArray(options.touchedViewIds) && options.touchedViewIds.length > 0
|
|
242
|
+
? new Set(options.touchedViewIds)
|
|
243
|
+
: undefined;
|
|
244
|
+
|
|
245
|
+
for (const view of document.views || []) {
|
|
246
|
+
if (!view) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (touchedViewIdSet && !touchedViewIdSet.has(view.view_id)) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const elementCount = Array.isArray(view.included_elements) ? view.included_elements.length : 0;
|
|
253
|
+
if (elementCount > maxIncludedElements) {
|
|
254
|
+
errors.push(
|
|
255
|
+
`views '${view.view_id}' must contain at most ${maxIncludedElements} elements; found ${elementCount}. ` +
|
|
256
|
+
'Split the content into layered sub-views before adding more elements.',
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Per-element-type attribute contract (issue #3): required attributes, a
|
|
263
|
+
// controlled vocabulary per attribute (enumByAttr) and uniqueness per attribute.
|
|
264
|
+
// Supplied by the active schema bundle's `attributesByElementType`; absent => no-op
|
|
265
|
+
// (fully backward compatible).
|
|
266
|
+
function attributeEntriesByName(element) {
|
|
267
|
+
const map = new Map();
|
|
268
|
+
const entries = Array.isArray(element && element.attributes) ? element.attributes : [];
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
if (!entry || typeof entry !== 'object' || typeof entry.name !== 'string' || entry.name === '') {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (!map.has(entry.name)) {
|
|
274
|
+
map.set(entry.name, []);
|
|
275
|
+
}
|
|
276
|
+
map.get(entry.name).push(entry);
|
|
277
|
+
}
|
|
278
|
+
return map;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function attributeValueOf(entry) {
|
|
282
|
+
if (entry.value !== undefined && entry.value !== null) {
|
|
283
|
+
return entry.value;
|
|
284
|
+
}
|
|
285
|
+
if (entry.content !== undefined && entry.content !== null) {
|
|
286
|
+
return entry.content;
|
|
287
|
+
}
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function validateAttributeContracts(document, errors, ontology) {
|
|
292
|
+
const language = resolveOntology(ontology);
|
|
293
|
+
const contracts = language.attributesByElementType;
|
|
294
|
+
if (!contracts || typeof contracts !== 'object') {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const uniqueTrackers = new Map();
|
|
298
|
+
for (const type of Object.keys(contracts)) {
|
|
299
|
+
uniqueTrackers.set(type, new Map());
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const element of document.elements || []) {
|
|
303
|
+
if (!element || typeof element !== 'object') {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const contract = contracts[element.type];
|
|
307
|
+
if (!contract || typeof contract !== 'object') {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const byName = attributeEntriesByName(element);
|
|
311
|
+
|
|
312
|
+
for (const name of Array.isArray(contract.required) ? contract.required : []) {
|
|
313
|
+
const entries = byName.get(name);
|
|
314
|
+
const present = Array.isArray(entries) && entries.some((entry) => {
|
|
315
|
+
const value = attributeValueOf(entry);
|
|
316
|
+
return value !== undefined && value !== '';
|
|
317
|
+
});
|
|
318
|
+
if (!present) {
|
|
319
|
+
errors.push(`element '${element.id}' (type '${element.type}') is missing required attribute '${name}'`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const enumByAttr = contract.enumByAttr && typeof contract.enumByAttr === 'object' ? contract.enumByAttr : {};
|
|
324
|
+
for (const [attr, allowed] of Object.entries(enumByAttr)) {
|
|
325
|
+
if (!Array.isArray(allowed)) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
for (const entry of byName.get(attr) || []) {
|
|
329
|
+
const value = attributeValueOf(entry);
|
|
330
|
+
if (value === undefined) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const matches = allowed.some((option) => option === value || JSON.stringify(option) === JSON.stringify(value));
|
|
334
|
+
if (!matches) {
|
|
335
|
+
errors.push(`element '${element.id}' (type '${element.type}') attribute '${attr}' value ${JSON.stringify(value)} is not one of: ${allowed.map((option) => JSON.stringify(option)).join(', ')}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const attr of Array.isArray(contract.unique) ? contract.unique : []) {
|
|
341
|
+
const tracker = uniqueTrackers.get(element.type);
|
|
342
|
+
for (const entry of byName.get(attr) || []) {
|
|
343
|
+
const value = attributeValueOf(entry);
|
|
344
|
+
if (value === undefined) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const key = JSON.stringify(value);
|
|
348
|
+
if (tracker.has(key) && tracker.get(key) !== element.id) {
|
|
349
|
+
errors.push(`element '${element.id}' (type '${element.type}') attribute '${attr}' value ${key} duplicates element '${tracker.get(key)}'`);
|
|
350
|
+
} else if (!tracker.has(key)) {
|
|
351
|
+
tracker.set(key, element.id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
validateGraphSemantics,
|
|
360
|
+
validateArchiMateEndpointMatrix,
|
|
361
|
+
validateViewElementLimits,
|
|
362
|
+
validateAttributeContracts,
|
|
363
|
+
};
|