@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.
Files changed (204) hide show
  1. package/README.md +126 -0
  2. package/dist/ConfigurationLoader.d.ts +76 -0
  3. package/dist/ConfigurationLoader.d.ts.map +1 -0
  4. package/dist/ConfigurationLoader.js +144 -0
  5. package/dist/ConfigurationLoader.js.map +1 -0
  6. package/dist/ConfigurationValidator.d.ts +31 -0
  7. package/dist/ConfigurationValidator.d.ts.map +1 -0
  8. package/dist/ConfigurationValidator.js +242 -0
  9. package/dist/ConfigurationValidator.js.map +1 -0
  10. package/dist/EventProcessor.d.ts +49 -0
  11. package/dist/EventProcessor.d.ts.map +1 -0
  12. package/dist/EventProcessor.js +215 -0
  13. package/dist/EventProcessor.js.map +1 -0
  14. package/dist/EventRecorderService.d.ts +305 -0
  15. package/dist/EventRecorderService.d.ts.map +1 -0
  16. package/dist/EventRecorderService.js +463 -0
  17. package/dist/EventRecorderService.js.map +1 -0
  18. package/dist/LibraryLoader.d.ts +63 -0
  19. package/dist/LibraryLoader.d.ts.map +1 -0
  20. package/dist/LibraryLoader.js +188 -0
  21. package/dist/LibraryLoader.js.map +1 -0
  22. package/dist/PathBasedEventProcessor.d.ts +90 -0
  23. package/dist/PathBasedEventProcessor.d.ts.map +1 -0
  24. package/dist/PathBasedEventProcessor.js +239 -0
  25. package/dist/PathBasedEventProcessor.js.map +1 -0
  26. package/dist/SessionManager.d.ts +194 -0
  27. package/dist/SessionManager.d.ts.map +1 -0
  28. package/dist/SessionManager.js +299 -0
  29. package/dist/SessionManager.js.map +1 -0
  30. package/dist/ValidationEngine.d.ts +31 -0
  31. package/dist/ValidationEngine.d.ts.map +1 -0
  32. package/dist/ValidationEngine.js +158 -0
  33. package/dist/ValidationEngine.js.map +1 -0
  34. package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
  35. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
  36. package/dist/helpers/GraphInstrumentationHelper.js +248 -0
  37. package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +34 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/rules/config.d.ts +57 -0
  43. package/dist/rules/config.d.ts.map +1 -0
  44. package/dist/rules/config.js +382 -0
  45. package/dist/rules/config.js.map +1 -0
  46. package/dist/rules/engine.d.ts +70 -0
  47. package/dist/rules/engine.d.ts.map +1 -0
  48. package/dist/rules/engine.js +252 -0
  49. package/dist/rules/engine.js.map +1 -0
  50. package/dist/rules/implementations/connection-type-references.d.ts +7 -0
  51. package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
  52. package/dist/rules/implementations/connection-type-references.js +104 -0
  53. package/dist/rules/implementations/connection-type-references.js.map +1 -0
  54. package/dist/rules/implementations/dead-end-states.d.ts +17 -0
  55. package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
  56. package/dist/rules/implementations/dead-end-states.js +72 -0
  57. package/dist/rules/implementations/dead-end-states.js.map +1 -0
  58. package/dist/rules/implementations/index.d.ts +24 -0
  59. package/dist/rules/implementations/index.d.ts.map +1 -0
  60. package/dist/rules/implementations/index.js +62 -0
  61. package/dist/rules/implementations/index.js.map +1 -0
  62. package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
  63. package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
  64. package/dist/rules/implementations/library-node-type-match.js +123 -0
  65. package/dist/rules/implementations/library-node-type-match.js.map +1 -0
  66. package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
  67. package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
  68. package/dist/rules/implementations/minimum-node-sources.js +54 -0
  69. package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
  70. package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
  71. package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
  72. package/dist/rules/implementations/no-unknown-fields.js +211 -0
  73. package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
  74. package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
  75. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
  76. package/dist/rules/implementations/orphaned-edge-types.js +47 -0
  77. package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
  78. package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
  79. package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
  80. package/dist/rules/implementations/orphaned-node-types.js +50 -0
  81. package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
  82. package/dist/rules/implementations/required-metadata.d.ts +7 -0
  83. package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
  84. package/dist/rules/implementations/required-metadata.js +57 -0
  85. package/dist/rules/implementations/required-metadata.js.map +1 -0
  86. package/dist/rules/implementations/state-transition-references.d.ts +7 -0
  87. package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
  88. package/dist/rules/implementations/state-transition-references.js +135 -0
  89. package/dist/rules/implementations/state-transition-references.js.map +1 -0
  90. package/dist/rules/implementations/unreachable-states.d.ts +7 -0
  91. package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
  92. package/dist/rules/implementations/unreachable-states.js +80 -0
  93. package/dist/rules/implementations/unreachable-states.js.map +1 -0
  94. package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
  95. package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
  96. package/dist/rules/implementations/valid-action-patterns.js +109 -0
  97. package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
  98. package/dist/rules/implementations/valid-color-format.d.ts +7 -0
  99. package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
  100. package/dist/rules/implementations/valid-color-format.js +91 -0
  101. package/dist/rules/implementations/valid-color-format.js.map +1 -0
  102. package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
  103. package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
  104. package/dist/rules/implementations/valid-edge-types.js +244 -0
  105. package/dist/rules/implementations/valid-edge-types.js.map +1 -0
  106. package/dist/rules/implementations/valid-node-types.d.ts +7 -0
  107. package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
  108. package/dist/rules/implementations/valid-node-types.js +175 -0
  109. package/dist/rules/implementations/valid-node-types.js.map +1 -0
  110. package/dist/rules/index.d.ts +28 -0
  111. package/dist/rules/index.d.ts.map +1 -0
  112. package/dist/rules/index.js +45 -0
  113. package/dist/rules/index.js.map +1 -0
  114. package/dist/rules/types.d.ts +309 -0
  115. package/dist/rules/types.d.ts.map +1 -0
  116. package/dist/rules/types.js +35 -0
  117. package/dist/rules/types.js.map +1 -0
  118. package/dist/types/canvas.d.ts +409 -0
  119. package/dist/types/canvas.d.ts.map +1 -0
  120. package/dist/types/canvas.js +70 -0
  121. package/dist/types/canvas.js.map +1 -0
  122. package/dist/types/index.d.ts +311 -0
  123. package/dist/types/index.d.ts.map +1 -0
  124. package/dist/types/index.js +13 -0
  125. package/dist/types/index.js.map +1 -0
  126. package/dist/types/library.d.ts +185 -0
  127. package/dist/types/library.d.ts.map +1 -0
  128. package/dist/types/library.js +15 -0
  129. package/dist/types/library.js.map +1 -0
  130. package/dist/types/path-based-config.d.ts +230 -0
  131. package/dist/types/path-based-config.d.ts.map +1 -0
  132. package/dist/types/path-based-config.js +9 -0
  133. package/dist/types/path-based-config.js.map +1 -0
  134. package/dist/utils/CanvasConverter.d.ts +118 -0
  135. package/dist/utils/CanvasConverter.d.ts.map +1 -0
  136. package/dist/utils/CanvasConverter.js +315 -0
  137. package/dist/utils/CanvasConverter.js.map +1 -0
  138. package/dist/utils/GraphConverter.d.ts +18 -0
  139. package/dist/utils/GraphConverter.d.ts.map +1 -0
  140. package/dist/utils/GraphConverter.js +61 -0
  141. package/dist/utils/GraphConverter.js.map +1 -0
  142. package/dist/utils/LibraryConverter.d.ts +113 -0
  143. package/dist/utils/LibraryConverter.d.ts.map +1 -0
  144. package/dist/utils/LibraryConverter.js +166 -0
  145. package/dist/utils/LibraryConverter.js.map +1 -0
  146. package/dist/utils/PathMatcher.d.ts +55 -0
  147. package/dist/utils/PathMatcher.d.ts.map +1 -0
  148. package/dist/utils/PathMatcher.js +172 -0
  149. package/dist/utils/PathMatcher.js.map +1 -0
  150. package/dist/utils/YamlParser.d.ts +36 -0
  151. package/dist/utils/YamlParser.d.ts.map +1 -0
  152. package/dist/utils/YamlParser.js +63 -0
  153. package/dist/utils/YamlParser.js.map +1 -0
  154. package/package.json +47 -0
  155. package/src/ConfigurationLoader.test.ts +490 -0
  156. package/src/ConfigurationLoader.ts +185 -0
  157. package/src/ConfigurationValidator.test.ts +200 -0
  158. package/src/ConfigurationValidator.ts +283 -0
  159. package/src/EventProcessor.test.ts +405 -0
  160. package/src/EventProcessor.ts +250 -0
  161. package/src/EventRecorderService.test.ts +541 -0
  162. package/src/EventRecorderService.ts +744 -0
  163. package/src/LibraryLoader.ts +215 -0
  164. package/src/PathBasedEventProcessor.test.ts +567 -0
  165. package/src/PathBasedEventProcessor.ts +332 -0
  166. package/src/SessionManager.test.ts +424 -0
  167. package/src/SessionManager.ts +470 -0
  168. package/src/ValidationEngine.test.ts +371 -0
  169. package/src/ValidationEngine.ts +196 -0
  170. package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
  171. package/src/helpers/GraphInstrumentationHelper.ts +326 -0
  172. package/src/index.ts +85 -0
  173. package/src/rules/config.test.ts +278 -0
  174. package/src/rules/config.ts +459 -0
  175. package/src/rules/engine.test.ts +332 -0
  176. package/src/rules/engine.ts +318 -0
  177. package/src/rules/implementations/connection-type-references.ts +117 -0
  178. package/src/rules/implementations/dead-end-states.ts +101 -0
  179. package/src/rules/implementations/index.ts +73 -0
  180. package/src/rules/implementations/library-node-type-match.ts +148 -0
  181. package/src/rules/implementations/minimum-node-sources.ts +82 -0
  182. package/src/rules/implementations/no-unknown-fields.ts +342 -0
  183. package/src/rules/implementations/orphaned-edge-types.ts +55 -0
  184. package/src/rules/implementations/orphaned-node-types.ts +58 -0
  185. package/src/rules/implementations/required-metadata.ts +64 -0
  186. package/src/rules/implementations/state-transition-references.ts +151 -0
  187. package/src/rules/implementations/unreachable-states.ts +94 -0
  188. package/src/rules/implementations/valid-action-patterns.ts +136 -0
  189. package/src/rules/implementations/valid-color-format.ts +140 -0
  190. package/src/rules/implementations/valid-edge-types.ts +258 -0
  191. package/src/rules/implementations/valid-node-types.ts +189 -0
  192. package/src/rules/index.ts +95 -0
  193. package/src/rules/types.ts +426 -0
  194. package/src/types/canvas.ts +496 -0
  195. package/src/types/index.ts +382 -0
  196. package/src/types/library.ts +233 -0
  197. package/src/types/path-based-config.ts +281 -0
  198. package/src/utils/CanvasConverter.ts +431 -0
  199. package/src/utils/GraphConverter.test.ts +195 -0
  200. package/src/utils/GraphConverter.ts +71 -0
  201. package/src/utils/LibraryConverter.ts +245 -0
  202. package/src/utils/PathMatcher.test.ts +148 -0
  203. package/src/utils/PathMatcher.ts +183 -0
  204. 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
+ }