@principal-ai/principal-view-core 0.5.6
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 +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- package/src/utils/YamlParser.ts +75 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: no-unknown-fields
|
|
3
|
+
* Flags fields that are not defined in the configuration schema
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Allowed fields for each configuration section
|
|
10
|
+
*/
|
|
11
|
+
const ALLOWED_FIELDS = {
|
|
12
|
+
root: ['metadata', 'nodeTypes', 'edgeTypes', 'allowedConnections', 'validation', 'display'],
|
|
13
|
+
metadata: ['name', 'version', 'description'],
|
|
14
|
+
nodeType: [
|
|
15
|
+
'shape',
|
|
16
|
+
'icon',
|
|
17
|
+
'color',
|
|
18
|
+
'stroke',
|
|
19
|
+
'size',
|
|
20
|
+
'dataSchema',
|
|
21
|
+
'states',
|
|
22
|
+
'layout',
|
|
23
|
+
'sources',
|
|
24
|
+
'actions',
|
|
25
|
+
],
|
|
26
|
+
edgeType: [
|
|
27
|
+
'style',
|
|
28
|
+
'color',
|
|
29
|
+
'width',
|
|
30
|
+
'directed',
|
|
31
|
+
'animated',
|
|
32
|
+
'label',
|
|
33
|
+
'animation',
|
|
34
|
+
'dataSchema',
|
|
35
|
+
'activatedBy',
|
|
36
|
+
],
|
|
37
|
+
connectionRule: ['from', 'to', 'via', 'constraints'],
|
|
38
|
+
connectionConstraints: ['maxInstances', 'bidirectional', 'exclusive'],
|
|
39
|
+
validation: ['stateTransitions', 'constraints', 'cardinality'],
|
|
40
|
+
display: ['layout', 'theme', 'animations'],
|
|
41
|
+
displayTheme: ['primary', 'success', 'warning', 'danger', 'info'],
|
|
42
|
+
displayAnimations: ['enabled', 'speed'],
|
|
43
|
+
dataSchemaField: ['type', 'required', 'displayInLabel', 'label', 'displayInInfo'],
|
|
44
|
+
stateDefinition: ['color', 'icon', 'label'],
|
|
45
|
+
layoutHints: ['layer', 'cluster'],
|
|
46
|
+
edgeLabel: ['field', 'position'],
|
|
47
|
+
edgeAnimation: ['type', 'duration', 'color'],
|
|
48
|
+
nodeSize: ['width', 'height'],
|
|
49
|
+
stateTransition: ['from', 'to'],
|
|
50
|
+
constraint: ['id', 'description', 'check', 'severity'],
|
|
51
|
+
actionPattern: ['pattern', 'event', 'state', 'metadata'],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const noUnknownFields: GraphRule = {
|
|
55
|
+
id: 'no-unknown-fields',
|
|
56
|
+
name: 'No Unknown Fields',
|
|
57
|
+
description: 'Flag fields that are not defined in the configuration schema',
|
|
58
|
+
impact: 'Unknown fields are likely typos and will be ignored',
|
|
59
|
+
severity: 'error',
|
|
60
|
+
category: 'schema',
|
|
61
|
+
enabled: true,
|
|
62
|
+
fixable: true,
|
|
63
|
+
|
|
64
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
65
|
+
const violations: GraphRuleViolation[] = [];
|
|
66
|
+
const { configuration, configPath } = context;
|
|
67
|
+
|
|
68
|
+
// Check root level fields
|
|
69
|
+
checkFields(configuration as unknown as Record<string, unknown>, ALLOWED_FIELDS.root, '', configPath, violations);
|
|
70
|
+
|
|
71
|
+
// Check metadata fields
|
|
72
|
+
if (configuration.metadata) {
|
|
73
|
+
checkFields(
|
|
74
|
+
configuration.metadata as Record<string, unknown>,
|
|
75
|
+
ALLOWED_FIELDS.metadata,
|
|
76
|
+
'metadata',
|
|
77
|
+
configPath,
|
|
78
|
+
violations
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check nodeTypes
|
|
83
|
+
if (configuration.nodeTypes) {
|
|
84
|
+
for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
|
|
85
|
+
const basePath = `nodeTypes.${typeId}`;
|
|
86
|
+
checkFields(nodeType as unknown as Record<string, unknown>, ALLOWED_FIELDS.nodeType, basePath, configPath, violations);
|
|
87
|
+
|
|
88
|
+
// Check nested fields in nodeType
|
|
89
|
+
if (nodeType.dataSchema) {
|
|
90
|
+
for (const [fieldName, fieldDef] of Object.entries(nodeType.dataSchema)) {
|
|
91
|
+
checkFields(
|
|
92
|
+
fieldDef as Record<string, unknown>,
|
|
93
|
+
ALLOWED_FIELDS.dataSchemaField,
|
|
94
|
+
`${basePath}.dataSchema.${fieldName}`,
|
|
95
|
+
configPath,
|
|
96
|
+
violations
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (nodeType.states) {
|
|
102
|
+
for (const [stateName, stateDef] of Object.entries(nodeType.states)) {
|
|
103
|
+
checkFields(
|
|
104
|
+
stateDef as Record<string, unknown>,
|
|
105
|
+
ALLOWED_FIELDS.stateDefinition,
|
|
106
|
+
`${basePath}.states.${stateName}`,
|
|
107
|
+
configPath,
|
|
108
|
+
violations
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (nodeType.layout) {
|
|
114
|
+
checkFields(
|
|
115
|
+
nodeType.layout as Record<string, unknown>,
|
|
116
|
+
ALLOWED_FIELDS.layoutHints,
|
|
117
|
+
`${basePath}.layout`,
|
|
118
|
+
configPath,
|
|
119
|
+
violations
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (nodeType.size) {
|
|
124
|
+
checkFields(
|
|
125
|
+
nodeType.size as Record<string, unknown>,
|
|
126
|
+
ALLOWED_FIELDS.nodeSize,
|
|
127
|
+
`${basePath}.size`,
|
|
128
|
+
configPath,
|
|
129
|
+
violations
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check actions
|
|
134
|
+
const nodeTypeAny = nodeType as unknown as Record<string, unknown>;
|
|
135
|
+
if (Array.isArray(nodeTypeAny.actions)) {
|
|
136
|
+
(nodeTypeAny.actions as unknown[]).forEach((action: unknown, index: number) => {
|
|
137
|
+
if (typeof action === 'object' && action !== null) {
|
|
138
|
+
checkFields(
|
|
139
|
+
action as Record<string, unknown>,
|
|
140
|
+
ALLOWED_FIELDS.actionPattern,
|
|
141
|
+
`${basePath}.actions[${index}]`,
|
|
142
|
+
configPath,
|
|
143
|
+
violations
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check edgeTypes
|
|
152
|
+
if (configuration.edgeTypes) {
|
|
153
|
+
for (const [typeId, edgeType] of Object.entries(configuration.edgeTypes)) {
|
|
154
|
+
const basePath = `edgeTypes.${typeId}`;
|
|
155
|
+
checkFields(edgeType as unknown as Record<string, unknown>, ALLOWED_FIELDS.edgeType, basePath, configPath, violations);
|
|
156
|
+
|
|
157
|
+
if (edgeType.label && typeof edgeType.label === 'object') {
|
|
158
|
+
checkFields(
|
|
159
|
+
edgeType.label as Record<string, unknown>,
|
|
160
|
+
ALLOWED_FIELDS.edgeLabel,
|
|
161
|
+
`${basePath}.label`,
|
|
162
|
+
configPath,
|
|
163
|
+
violations
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (edgeType.animation && typeof edgeType.animation === 'object') {
|
|
168
|
+
checkFields(
|
|
169
|
+
edgeType.animation as Record<string, unknown>,
|
|
170
|
+
ALLOWED_FIELDS.edgeAnimation,
|
|
171
|
+
`${basePath}.animation`,
|
|
172
|
+
configPath,
|
|
173
|
+
violations
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (edgeType.dataSchema) {
|
|
178
|
+
for (const [fieldName, fieldDef] of Object.entries(edgeType.dataSchema)) {
|
|
179
|
+
checkFields(
|
|
180
|
+
fieldDef as Record<string, unknown>,
|
|
181
|
+
ALLOWED_FIELDS.dataSchemaField,
|
|
182
|
+
`${basePath}.dataSchema.${fieldName}`,
|
|
183
|
+
configPath,
|
|
184
|
+
violations
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check allowedConnections
|
|
192
|
+
if (configuration.allowedConnections) {
|
|
193
|
+
configuration.allowedConnections.forEach((rule, index) => {
|
|
194
|
+
const basePath = `allowedConnections[${index}]`;
|
|
195
|
+
checkFields(rule as unknown as Record<string, unknown>, ALLOWED_FIELDS.connectionRule, basePath, configPath, violations);
|
|
196
|
+
|
|
197
|
+
if (rule.constraints) {
|
|
198
|
+
checkFields(
|
|
199
|
+
rule.constraints as Record<string, unknown>,
|
|
200
|
+
ALLOWED_FIELDS.connectionConstraints,
|
|
201
|
+
`${basePath}.constraints`,
|
|
202
|
+
configPath,
|
|
203
|
+
violations
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check validation
|
|
210
|
+
if (configuration.validation) {
|
|
211
|
+
checkFields(
|
|
212
|
+
configuration.validation as Record<string, unknown>,
|
|
213
|
+
ALLOWED_FIELDS.validation,
|
|
214
|
+
'validation',
|
|
215
|
+
configPath,
|
|
216
|
+
violations
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Check state transitions
|
|
220
|
+
if (configuration.validation.stateTransitions) {
|
|
221
|
+
for (const [nodeType, transitions] of Object.entries(configuration.validation.stateTransitions)) {
|
|
222
|
+
if (Array.isArray(transitions)) {
|
|
223
|
+
transitions.forEach((transition, index) => {
|
|
224
|
+
checkFields(
|
|
225
|
+
transition as Record<string, unknown>,
|
|
226
|
+
ALLOWED_FIELDS.stateTransition,
|
|
227
|
+
`validation.stateTransitions.${nodeType}[${index}]`,
|
|
228
|
+
configPath,
|
|
229
|
+
violations
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check constraints
|
|
237
|
+
if (configuration.validation.constraints && Array.isArray(configuration.validation.constraints)) {
|
|
238
|
+
configuration.validation.constraints.forEach((constraint, index) => {
|
|
239
|
+
checkFields(
|
|
240
|
+
constraint as Record<string, unknown>,
|
|
241
|
+
ALLOWED_FIELDS.constraint,
|
|
242
|
+
`validation.constraints[${index}]`,
|
|
243
|
+
configPath,
|
|
244
|
+
violations
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check display
|
|
251
|
+
if (configuration.display) {
|
|
252
|
+
checkFields(
|
|
253
|
+
configuration.display as unknown as Record<string, unknown>,
|
|
254
|
+
ALLOWED_FIELDS.display,
|
|
255
|
+
'display',
|
|
256
|
+
configPath,
|
|
257
|
+
violations
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
if (configuration.display.theme) {
|
|
261
|
+
checkFields(
|
|
262
|
+
configuration.display.theme as Record<string, unknown>,
|
|
263
|
+
ALLOWED_FIELDS.displayTheme,
|
|
264
|
+
'display.theme',
|
|
265
|
+
configPath,
|
|
266
|
+
violations
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (configuration.display.animations) {
|
|
271
|
+
checkFields(
|
|
272
|
+
configuration.display.animations as Record<string, unknown>,
|
|
273
|
+
ALLOWED_FIELDS.displayAnimations,
|
|
274
|
+
'display.animations',
|
|
275
|
+
configPath,
|
|
276
|
+
violations
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return violations;
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Check an object's fields against allowed fields
|
|
287
|
+
*/
|
|
288
|
+
function checkFields(
|
|
289
|
+
obj: Record<string, unknown>,
|
|
290
|
+
allowedFields: string[],
|
|
291
|
+
path: string,
|
|
292
|
+
configPath: string | undefined,
|
|
293
|
+
violations: GraphRuleViolation[]
|
|
294
|
+
): void {
|
|
295
|
+
for (const field of Object.keys(obj)) {
|
|
296
|
+
if (!allowedFields.includes(field)) {
|
|
297
|
+
const suggestion = findSimilarField(field, allowedFields);
|
|
298
|
+
violations.push({
|
|
299
|
+
ruleId: 'no-unknown-fields',
|
|
300
|
+
severity: 'error',
|
|
301
|
+
file: configPath,
|
|
302
|
+
path: path ? `${path}.${field}` : field,
|
|
303
|
+
message: `Unknown field "${field}"${path ? ` in ${path}` : ' at root level'}`,
|
|
304
|
+
impact: 'This field will be ignored and may indicate a configuration error',
|
|
305
|
+
suggestion: suggestion
|
|
306
|
+
? `Did you mean "${suggestion}"? Allowed fields: ${allowedFields.join(', ')}`
|
|
307
|
+
: `Allowed fields: ${allowedFields.join(', ')}`,
|
|
308
|
+
fixable: true,
|
|
309
|
+
fixData: { path, field, suggestion },
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Find a similar field name for "did you mean" suggestions
|
|
317
|
+
*/
|
|
318
|
+
function findSimilarField(field: string, allowedFields: string[]): string | null {
|
|
319
|
+
const fieldLower = field.toLowerCase();
|
|
320
|
+
|
|
321
|
+
for (const allowed of allowedFields) {
|
|
322
|
+
const allowedLower = allowed.toLowerCase();
|
|
323
|
+
|
|
324
|
+
// Check for substring match
|
|
325
|
+
if (fieldLower.includes(allowedLower) || allowedLower.includes(fieldLower)) {
|
|
326
|
+
return allowed;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check for small edit distance (typos)
|
|
330
|
+
if (Math.abs(field.length - allowed.length) <= 2) {
|
|
331
|
+
let differences = 0;
|
|
332
|
+
const minLen = Math.min(fieldLower.length, allowedLower.length);
|
|
333
|
+
for (let i = 0; i < minLen; i++) {
|
|
334
|
+
if (fieldLower[i] !== allowedLower[i]) differences++;
|
|
335
|
+
}
|
|
336
|
+
differences += Math.abs(field.length - allowed.length);
|
|
337
|
+
if (differences <= 2) return allowed;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: orphaned-edge-types
|
|
3
|
+
* Warns when edgeTypes are not used in any connection rule
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
export const orphanedEdgeTypes: GraphRule = {
|
|
9
|
+
id: 'orphaned-edge-types',
|
|
10
|
+
name: 'Orphaned Edge Types',
|
|
11
|
+
description: 'EdgeTypes should be used in at least one connection rule',
|
|
12
|
+
impact: 'Orphaned edge types may indicate unused or misconfigured types',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
category: 'structure',
|
|
15
|
+
enabled: true,
|
|
16
|
+
fixable: false,
|
|
17
|
+
|
|
18
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
19
|
+
const violations: GraphRuleViolation[] = [];
|
|
20
|
+
const { configuration, configPath } = context;
|
|
21
|
+
|
|
22
|
+
if (!configuration.edgeTypes || Object.keys(configuration.edgeTypes).length === 0) {
|
|
23
|
+
return violations;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Collect all edgeTypes referenced in allowedConnections
|
|
27
|
+
const referencedEdgeTypes = new Set<string>();
|
|
28
|
+
|
|
29
|
+
if (configuration.allowedConnections) {
|
|
30
|
+
for (const connection of configuration.allowedConnections) {
|
|
31
|
+
if (connection.via) {
|
|
32
|
+
referencedEdgeTypes.add(connection.via);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check each edgeType
|
|
38
|
+
for (const typeId of Object.keys(configuration.edgeTypes)) {
|
|
39
|
+
if (!referencedEdgeTypes.has(typeId)) {
|
|
40
|
+
violations.push({
|
|
41
|
+
ruleId: 'orphaned-edge-types',
|
|
42
|
+
severity: 'error',
|
|
43
|
+
file: configPath,
|
|
44
|
+
path: `edgeTypes.${typeId}`,
|
|
45
|
+
message: `Edge type "${typeId}" is not used in any connection rule`,
|
|
46
|
+
impact: 'This edge type may be unused or misconfigured',
|
|
47
|
+
suggestion: `Add a connection rule using this edge type, or remove it if unused`,
|
|
48
|
+
fixable: false,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return violations;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: orphaned-node-types
|
|
3
|
+
* Warns when nodeTypes are not used in any connection rule
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
export const orphanedNodeTypes: GraphRule = {
|
|
9
|
+
id: 'orphaned-node-types',
|
|
10
|
+
name: 'Orphaned Node Types',
|
|
11
|
+
description: 'NodeTypes should be used in at least one connection rule',
|
|
12
|
+
impact: 'Orphaned node types may indicate unused or misconfigured types',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
category: 'structure',
|
|
15
|
+
enabled: true,
|
|
16
|
+
fixable: false,
|
|
17
|
+
|
|
18
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
19
|
+
const violations: GraphRuleViolation[] = [];
|
|
20
|
+
const { configuration, configPath } = context;
|
|
21
|
+
|
|
22
|
+
if (!configuration.nodeTypes || Object.keys(configuration.nodeTypes).length === 0) {
|
|
23
|
+
return violations;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Collect all nodeTypes referenced in allowedConnections
|
|
27
|
+
const referencedNodeTypes = new Set<string>();
|
|
28
|
+
|
|
29
|
+
if (configuration.allowedConnections) {
|
|
30
|
+
for (const connection of configuration.allowedConnections) {
|
|
31
|
+
if (connection.from) {
|
|
32
|
+
referencedNodeTypes.add(connection.from);
|
|
33
|
+
}
|
|
34
|
+
if (connection.to) {
|
|
35
|
+
referencedNodeTypes.add(connection.to);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check each nodeType
|
|
41
|
+
for (const typeId of Object.keys(configuration.nodeTypes)) {
|
|
42
|
+
if (!referencedNodeTypes.has(typeId)) {
|
|
43
|
+
violations.push({
|
|
44
|
+
ruleId: 'orphaned-node-types',
|
|
45
|
+
severity: 'error',
|
|
46
|
+
file: configPath,
|
|
47
|
+
path: `nodeTypes.${typeId}`,
|
|
48
|
+
message: `Node type "${typeId}" is not used in any connection rule`,
|
|
49
|
+
impact: 'This node type may be unused or misconfigured',
|
|
50
|
+
suggestion: `Add a connection rule using this node type, or remove it if unused`,
|
|
51
|
+
fixable: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return violations;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: required-metadata
|
|
3
|
+
* Ensures configuration has required metadata fields (name, version)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
export const requiredMetadata: GraphRule = {
|
|
9
|
+
id: 'required-metadata',
|
|
10
|
+
name: 'Required Metadata',
|
|
11
|
+
description: 'Configuration must have name and version in metadata',
|
|
12
|
+
impact: 'Missing metadata makes configurations hard to identify and version',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
category: 'schema',
|
|
15
|
+
enabled: true,
|
|
16
|
+
fixable: false,
|
|
17
|
+
|
|
18
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
19
|
+
const violations: GraphRuleViolation[] = [];
|
|
20
|
+
const { configuration, configPath } = context;
|
|
21
|
+
|
|
22
|
+
if (!configuration.metadata) {
|
|
23
|
+
violations.push({
|
|
24
|
+
ruleId: 'required-metadata',
|
|
25
|
+
severity: 'error',
|
|
26
|
+
file: configPath,
|
|
27
|
+
path: 'metadata',
|
|
28
|
+
message: 'Configuration is missing metadata section',
|
|
29
|
+
impact: 'Cannot identify or version this configuration',
|
|
30
|
+
suggestion: 'Add a metadata section with name and version fields',
|
|
31
|
+
fixable: false,
|
|
32
|
+
});
|
|
33
|
+
return violations;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!configuration.metadata.name) {
|
|
37
|
+
violations.push({
|
|
38
|
+
ruleId: 'required-metadata',
|
|
39
|
+
severity: 'error',
|
|
40
|
+
file: configPath,
|
|
41
|
+
path: 'metadata.name',
|
|
42
|
+
message: 'Configuration metadata is missing required "name" field',
|
|
43
|
+
impact: 'Cannot identify this configuration',
|
|
44
|
+
suggestion: 'Add a name field to metadata',
|
|
45
|
+
fixable: false,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!configuration.metadata.version) {
|
|
50
|
+
violations.push({
|
|
51
|
+
ruleId: 'required-metadata',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
file: configPath,
|
|
54
|
+
path: 'metadata.version',
|
|
55
|
+
message: 'Configuration metadata is missing required "version" field',
|
|
56
|
+
impact: 'Cannot track configuration versions',
|
|
57
|
+
suggestion: 'Add a version field (e.g., "1.0.0") to metadata',
|
|
58
|
+
fixable: false,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return violations;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: state-transition-references
|
|
3
|
+
* Ensures state transitions only reference defined states
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
export const stateTransitionReferences: GraphRule = {
|
|
9
|
+
id: 'state-transition-references',
|
|
10
|
+
name: 'State Transition References',
|
|
11
|
+
description: 'State transitions must reference states defined in nodeTypes',
|
|
12
|
+
impact: 'Invalid state references will cause state validation to fail',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
category: 'reference',
|
|
15
|
+
enabled: true,
|
|
16
|
+
fixable: false,
|
|
17
|
+
|
|
18
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
19
|
+
const violations: GraphRuleViolation[] = [];
|
|
20
|
+
const { configuration, configPath } = context;
|
|
21
|
+
|
|
22
|
+
if (!configuration.validation?.stateTransitions) {
|
|
23
|
+
return violations;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Build a map of nodeType -> defined states
|
|
27
|
+
const statesByNodeType = new Map<string, Set<string>>();
|
|
28
|
+
|
|
29
|
+
if (configuration.nodeTypes) {
|
|
30
|
+
for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
|
|
31
|
+
if (nodeType.states) {
|
|
32
|
+
statesByNodeType.set(typeId, new Set(Object.keys(nodeType.states)));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check each state transition rule
|
|
38
|
+
for (const [nodeTypeId, transitions] of Object.entries(configuration.validation.stateTransitions)) {
|
|
39
|
+
const basePath = `validation.stateTransitions.${nodeTypeId}`;
|
|
40
|
+
|
|
41
|
+
// Check if nodeType exists
|
|
42
|
+
if (!configuration.nodeTypes?.[nodeTypeId]) {
|
|
43
|
+
violations.push({
|
|
44
|
+
ruleId: 'state-transition-references',
|
|
45
|
+
severity: 'error',
|
|
46
|
+
file: configPath,
|
|
47
|
+
path: basePath,
|
|
48
|
+
message: `State transitions defined for undefined nodeType "${nodeTypeId}"`,
|
|
49
|
+
impact: 'These state transitions will never apply',
|
|
50
|
+
suggestion: `Available nodeTypes: ${Object.keys(configuration.nodeTypes ?? {}).join(', ') || '(none defined)'}`,
|
|
51
|
+
fixable: false,
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if nodeType has states defined
|
|
57
|
+
const definedStates = statesByNodeType.get(nodeTypeId);
|
|
58
|
+
if (!definedStates || definedStates.size === 0) {
|
|
59
|
+
violations.push({
|
|
60
|
+
ruleId: 'state-transition-references',
|
|
61
|
+
severity: 'error',
|
|
62
|
+
file: configPath,
|
|
63
|
+
path: basePath,
|
|
64
|
+
message: `State transitions defined for nodeType "${nodeTypeId}" which has no states`,
|
|
65
|
+
impact: 'These state transitions will never apply',
|
|
66
|
+
suggestion: `Add states to nodeTypes.${nodeTypeId}.states`,
|
|
67
|
+
fixable: false,
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check each transition
|
|
73
|
+
if (Array.isArray(transitions)) {
|
|
74
|
+
for (let i = 0; i < transitions.length; i++) {
|
|
75
|
+
const transition = transitions[i];
|
|
76
|
+
const transitionPath = `${basePath}[${i}]`;
|
|
77
|
+
|
|
78
|
+
// Check 'from' state
|
|
79
|
+
if (transition.from && !definedStates.has(transition.from)) {
|
|
80
|
+
const suggestion = findSimilar(transition.from, definedStates);
|
|
81
|
+
violations.push({
|
|
82
|
+
ruleId: 'state-transition-references',
|
|
83
|
+
severity: 'error',
|
|
84
|
+
file: configPath,
|
|
85
|
+
path: `${transitionPath}.from`,
|
|
86
|
+
message: `Transition references undefined state "${transition.from}" in nodeType "${nodeTypeId}"`,
|
|
87
|
+
impact: 'This transition will never match',
|
|
88
|
+
suggestion: suggestion
|
|
89
|
+
? `Did you mean "${suggestion}"?`
|
|
90
|
+
: `Available states: ${[...definedStates].join(', ')}`,
|
|
91
|
+
fixable: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check 'to' states
|
|
96
|
+
if (Array.isArray(transition.to)) {
|
|
97
|
+
for (let j = 0; j < transition.to.length; j++) {
|
|
98
|
+
const toState = transition.to[j];
|
|
99
|
+
if (!definedStates.has(toState)) {
|
|
100
|
+
const suggestion = findSimilar(toState, definedStates);
|
|
101
|
+
violations.push({
|
|
102
|
+
ruleId: 'state-transition-references',
|
|
103
|
+
severity: 'error',
|
|
104
|
+
file: configPath,
|
|
105
|
+
path: `${transitionPath}.to[${j}]`,
|
|
106
|
+
message: `Transition references undefined state "${toState}" in nodeType "${nodeTypeId}"`,
|
|
107
|
+
impact: 'This transition target is invalid',
|
|
108
|
+
suggestion: suggestion
|
|
109
|
+
? `Did you mean "${suggestion}"?`
|
|
110
|
+
: `Available states: ${[...definedStates].join(', ')}`,
|
|
111
|
+
fixable: false,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return violations;
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Find a similar string from a set (for "did you mean" suggestions)
|
|
126
|
+
*/
|
|
127
|
+
function findSimilar(input: string, candidates: Set<string>): string | null {
|
|
128
|
+
const inputLower = input.toLowerCase();
|
|
129
|
+
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
const candidateLower = candidate.toLowerCase();
|
|
132
|
+
|
|
133
|
+
// Exact substring match
|
|
134
|
+
if (inputLower.includes(candidateLower) || candidateLower.includes(inputLower)) {
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Small edit distance
|
|
139
|
+
if (Math.abs(input.length - candidate.length) <= 2) {
|
|
140
|
+
let differences = 0;
|
|
141
|
+
const minLen = Math.min(inputLower.length, candidateLower.length);
|
|
142
|
+
for (let i = 0; i < minLen; i++) {
|
|
143
|
+
if (inputLower[i] !== candidateLower[i]) differences++;
|
|
144
|
+
}
|
|
145
|
+
differences += Math.abs(input.length - candidate.length);
|
|
146
|
+
if (differences <= 2) return candidate;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null;
|
|
151
|
+
}
|