@forwardimpact/schema 0.1.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/bin/fit-schema.js +260 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +43 -0
- package/examples/behaviours/polymathic_knowledge.yaml +41 -0
- package/examples/behaviours/precise_communication.yaml +39 -0
- package/examples/behaviours/relentless_curiosity.yaml +37 -0
- package/examples/behaviours/systems_thinking.yaml +40 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +189 -0
- package/examples/capabilities/delivery.yaml +305 -0
- package/examples/capabilities/people.yaml +68 -0
- package/examples/capabilities/reliability.yaml +414 -0
- package/examples/capabilities/scale.yaml +378 -0
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +6 -0
- package/examples/disciplines/data_engineering.yaml +78 -0
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +78 -0
- package/examples/drivers.yaml +202 -0
- package/examples/framework.yaml +69 -0
- package/examples/grades.yaml +115 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +51 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +47 -0
- package/examples/questions/behaviours/precise_communication.yaml +54 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +50 -0
- package/examples/questions/behaviours/systems_thinking.yaml +52 -0
- package/examples/questions/skills/architecture_design.yaml +53 -0
- package/examples/questions/skills/cloud_platforms.yaml +47 -0
- package/examples/questions/skills/code_quality.yaml +48 -0
- package/examples/questions/skills/data_modeling.yaml +45 -0
- package/examples/questions/skills/devops.yaml +46 -0
- package/examples/questions/skills/full_stack_development.yaml +47 -0
- package/examples/questions/skills/sre_practices.yaml +43 -0
- package/examples/questions/skills/stakeholder_management.yaml +48 -0
- package/examples/questions/skills/team_collaboration.yaml +42 -0
- package/examples/questions/skills/technical_writing.yaml +42 -0
- package/examples/self-assessments.yaml +64 -0
- package/examples/stages.yaml +139 -0
- package/examples/tracks/_index.yaml +5 -0
- package/examples/tracks/platform.yaml +49 -0
- package/examples/tracks/sre.yaml +48 -0
- package/examples/vscode-settings.yaml +21 -0
- package/lib/index-generator.js +65 -0
- package/lib/index.js +44 -0
- package/lib/levels.js +601 -0
- package/lib/loader.js +599 -0
- package/lib/modifiers.js +23 -0
- package/lib/schema-validation.js +438 -0
- package/lib/validation.js +2130 -0
- package/package.json +49 -0
- package/schema/json/behaviour-questions.schema.json +68 -0
- package/schema/json/behaviour.schema.json +73 -0
- package/schema/json/capability.schema.json +220 -0
- package/schema/json/defs.schema.json +132 -0
- package/schema/json/discipline.schema.json +132 -0
- package/schema/json/drivers.schema.json +48 -0
- package/schema/json/framework.schema.json +55 -0
- package/schema/json/grades.schema.json +121 -0
- package/schema/json/index.schema.json +18 -0
- package/schema/json/self-assessments.schema.json +52 -0
- package/schema/json/skill-questions.schema.json +68 -0
- package/schema/json/stages.schema.json +84 -0
- package/schema/json/track.schema.json +100 -0
- package/schema/rdf/pathway.ttl +2362 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-based Validation for Engineering Pathway
|
|
3
|
+
*
|
|
4
|
+
* Validates YAML data files against JSON schemas using Ajv.
|
|
5
|
+
* Replaces custom validation with declarative schema validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
import { join, dirname } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import Ajv from "ajv";
|
|
13
|
+
import addFormats from "ajv-formats";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
const schemaDir = join(__dirname, "../schema/json");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Schema mappings for different file types
|
|
21
|
+
* Maps directory/file patterns to schema files
|
|
22
|
+
*/
|
|
23
|
+
const SCHEMA_MAPPINGS = {
|
|
24
|
+
// Single files at root of data directory
|
|
25
|
+
"drivers.yaml": "drivers.schema.json",
|
|
26
|
+
"grades.yaml": "grades.schema.json",
|
|
27
|
+
"stages.yaml": "stages.schema.json",
|
|
28
|
+
"framework.yaml": "framework.schema.json",
|
|
29
|
+
"self-assessments.yaml": "self-assessments.schema.json",
|
|
30
|
+
// Directories - each file in directory uses the schema
|
|
31
|
+
capabilities: "capability.schema.json",
|
|
32
|
+
disciplines: "discipline.schema.json",
|
|
33
|
+
tracks: "track.schema.json",
|
|
34
|
+
behaviours: "behaviour.schema.json",
|
|
35
|
+
"questions/skills": "skill-questions.schema.json",
|
|
36
|
+
"questions/behaviours": "behaviour-questions.schema.json",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Create a validation result object
|
|
41
|
+
* @param {boolean} valid - Whether validation passed
|
|
42
|
+
* @param {Array<{type: string, message: string, path?: string}>} errors - Array of errors
|
|
43
|
+
* @param {Array<{type: string, message: string, path?: string}>} warnings - Array of warnings
|
|
44
|
+
* @returns {{valid: boolean, errors: Array, warnings: Array}}
|
|
45
|
+
*/
|
|
46
|
+
function createValidationResult(valid, errors = [], warnings = []) {
|
|
47
|
+
return { valid, errors, warnings };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create a validation error
|
|
52
|
+
* @param {string} type - Error type
|
|
53
|
+
* @param {string} message - Error message
|
|
54
|
+
* @param {string} [path] - Path to invalid data
|
|
55
|
+
* @returns {{type: string, message: string, path?: string}}
|
|
56
|
+
*/
|
|
57
|
+
function createError(type, message, path) {
|
|
58
|
+
const error = { type, message };
|
|
59
|
+
if (path !== undefined) error.path = path;
|
|
60
|
+
return error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create a validation warning
|
|
65
|
+
* @param {string} type - Warning type
|
|
66
|
+
* @param {string} message - Warning message
|
|
67
|
+
* @param {string} [path] - Path to concerning data
|
|
68
|
+
* @returns {{type: string, message: string, path?: string}}
|
|
69
|
+
*/
|
|
70
|
+
function createWarning(type, message, path) {
|
|
71
|
+
const warning = { type, message };
|
|
72
|
+
if (path !== undefined) warning.path = path;
|
|
73
|
+
return warning;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a path exists and is a directory
|
|
78
|
+
* @param {string} path - Path to check
|
|
79
|
+
* @returns {Promise<boolean>}
|
|
80
|
+
*/
|
|
81
|
+
async function isDirectory(path) {
|
|
82
|
+
try {
|
|
83
|
+
const stats = await stat(path);
|
|
84
|
+
return stats.isDirectory();
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a file exists
|
|
92
|
+
* @param {string} path - Path to check
|
|
93
|
+
* @returns {Promise<boolean>}
|
|
94
|
+
*/
|
|
95
|
+
async function fileExists(path) {
|
|
96
|
+
try {
|
|
97
|
+
await stat(path);
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Load and parse a JSON schema
|
|
106
|
+
* @param {string} schemaPath - Path to the schema file
|
|
107
|
+
* @returns {Promise<Object>} Parsed schema
|
|
108
|
+
*/
|
|
109
|
+
async function loadSchema(schemaPath) {
|
|
110
|
+
const content = await readFile(schemaPath, "utf-8");
|
|
111
|
+
return JSON.parse(content);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Load and parse a YAML file
|
|
116
|
+
* @param {string} filePath - Path to the YAML file
|
|
117
|
+
* @returns {Promise<any>} Parsed YAML content
|
|
118
|
+
*/
|
|
119
|
+
async function loadYamlFile(filePath) {
|
|
120
|
+
const content = await readFile(filePath, "utf-8");
|
|
121
|
+
return parseYaml(content);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Format Ajv errors into readable messages
|
|
126
|
+
* @param {import('ajv').ErrorObject[]} ajvErrors - Ajv error objects
|
|
127
|
+
* @param {string} filePath - File being validated
|
|
128
|
+
* @returns {Array<{type: string, message: string, path?: string}>}
|
|
129
|
+
*/
|
|
130
|
+
function formatAjvErrors(ajvErrors, filePath) {
|
|
131
|
+
return ajvErrors.map((err) => {
|
|
132
|
+
const path = err.instancePath ? `${filePath}${err.instancePath}` : filePath;
|
|
133
|
+
let message = err.message || "Unknown error";
|
|
134
|
+
|
|
135
|
+
// Add context for specific error types
|
|
136
|
+
if (err.keyword === "additionalProperties") {
|
|
137
|
+
message = `${message}: '${err.params.additionalProperty}'`;
|
|
138
|
+
} else if (err.keyword === "enum") {
|
|
139
|
+
message = `${message}. Allowed: ${err.params.allowedValues.join(", ")}`;
|
|
140
|
+
} else if (err.keyword === "pattern") {
|
|
141
|
+
message = `${message}. Pattern: ${err.params.pattern}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return createError("SCHEMA_VALIDATION", message, path);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create and configure an Ajv instance with all schemas loaded
|
|
150
|
+
* @returns {Promise<Ajv>}
|
|
151
|
+
*/
|
|
152
|
+
async function createValidator() {
|
|
153
|
+
const ajv = new Ajv({
|
|
154
|
+
allErrors: true,
|
|
155
|
+
strict: false,
|
|
156
|
+
validateFormats: true,
|
|
157
|
+
});
|
|
158
|
+
addFormats(ajv);
|
|
159
|
+
|
|
160
|
+
// Load all schema files
|
|
161
|
+
const schemaFiles = await readdir(schemaDir);
|
|
162
|
+
for (const file of schemaFiles.filter((f) => f.endsWith(".schema.json"))) {
|
|
163
|
+
const schema = await loadSchema(join(schemaDir, file));
|
|
164
|
+
ajv.addSchema(schema);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return ajv;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Validate a single file against a schema
|
|
172
|
+
* @param {Ajv} ajv - Configured Ajv instance
|
|
173
|
+
* @param {string} filePath - Path to the YAML file
|
|
174
|
+
* @param {string} schemaId - Schema $id to validate against
|
|
175
|
+
* @returns {Promise<{valid: boolean, errors: Array}>}
|
|
176
|
+
*/
|
|
177
|
+
async function validateFile(ajv, filePath, schemaId) {
|
|
178
|
+
const data = await loadYamlFile(filePath);
|
|
179
|
+
const validate = ajv.getSchema(schemaId);
|
|
180
|
+
|
|
181
|
+
if (!validate) {
|
|
182
|
+
return {
|
|
183
|
+
valid: false,
|
|
184
|
+
errors: [
|
|
185
|
+
createError("SCHEMA_NOT_FOUND", `Schema not found: ${schemaId}`),
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const valid = validate(data);
|
|
191
|
+
const errors = valid ? [] : formatAjvErrors(validate.errors || [], filePath);
|
|
192
|
+
|
|
193
|
+
return { valid, errors };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Validate all files in a directory against a schema
|
|
198
|
+
* @param {Ajv} ajv - Configured Ajv instance
|
|
199
|
+
* @param {string} dirPath - Path to the directory
|
|
200
|
+
* @param {string} schemaId - Schema $id to validate against
|
|
201
|
+
* @returns {Promise<{valid: boolean, errors: Array}>}
|
|
202
|
+
*/
|
|
203
|
+
async function validateDirectory(ajv, dirPath, schemaId) {
|
|
204
|
+
const files = await readdir(dirPath);
|
|
205
|
+
const yamlFiles = files.filter(
|
|
206
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const allErrors = [];
|
|
210
|
+
let allValid = true;
|
|
211
|
+
|
|
212
|
+
for (const file of yamlFiles) {
|
|
213
|
+
const filePath = join(dirPath, file);
|
|
214
|
+
const result = await validateFile(ajv, filePath, schemaId);
|
|
215
|
+
if (!result.valid) {
|
|
216
|
+
allValid = false;
|
|
217
|
+
allErrors.push(...result.errors);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return { valid: allValid, errors: allErrors };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Build the schema $id from the schema filename
|
|
226
|
+
* @param {string} schemaFilename - Schema filename (e.g., "capability.schema.json")
|
|
227
|
+
* @returns {string} Full schema $id URL
|
|
228
|
+
*/
|
|
229
|
+
function getSchemaId(schemaFilename) {
|
|
230
|
+
return `https://schema.forwardimpact.team/json/${schemaFilename}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Validate a data directory against JSON schemas
|
|
235
|
+
* @param {string} dataDir - Path to the data directory
|
|
236
|
+
* @returns {Promise<{valid: boolean, errors: Array, warnings: Array, stats: Object}>}
|
|
237
|
+
*/
|
|
238
|
+
export async function validateDataDirectory(dataDir) {
|
|
239
|
+
const ajv = await createValidator();
|
|
240
|
+
const allErrors = [];
|
|
241
|
+
const warnings = [];
|
|
242
|
+
const stats = {
|
|
243
|
+
filesValidated: 0,
|
|
244
|
+
schemasUsed: new Set(),
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Validate single files at root level
|
|
248
|
+
for (const [filename, schemaFile] of Object.entries(SCHEMA_MAPPINGS)) {
|
|
249
|
+
// Skip directory mappings
|
|
250
|
+
if (!filename.includes(".yaml")) continue;
|
|
251
|
+
|
|
252
|
+
const filePath = join(dataDir, filename);
|
|
253
|
+
if (!(await fileExists(filePath))) {
|
|
254
|
+
// Some files are optional
|
|
255
|
+
if (!["self-assessments.yaml"].includes(filename)) {
|
|
256
|
+
warnings.push(
|
|
257
|
+
createWarning("MISSING_FILE", `Optional file not found: ${filename}`),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const schemaId = getSchemaId(schemaFile);
|
|
264
|
+
const result = await validateFile(ajv, filePath, schemaId);
|
|
265
|
+
stats.filesValidated++;
|
|
266
|
+
stats.schemasUsed.add(schemaFile);
|
|
267
|
+
|
|
268
|
+
if (!result.valid) {
|
|
269
|
+
allErrors.push(...result.errors);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Validate directories
|
|
274
|
+
for (const [dirName, schemaFile] of Object.entries(SCHEMA_MAPPINGS)) {
|
|
275
|
+
// Skip single file mappings
|
|
276
|
+
if (dirName.includes(".yaml")) continue;
|
|
277
|
+
|
|
278
|
+
const dirPath = join(dataDir, dirName);
|
|
279
|
+
if (!(await isDirectory(dirPath))) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const schemaId = getSchemaId(schemaFile);
|
|
284
|
+
const result = await validateDirectory(ajv, dirPath, schemaId);
|
|
285
|
+
|
|
286
|
+
// Count files
|
|
287
|
+
const files = await readdir(dirPath);
|
|
288
|
+
const yamlFiles = files.filter(
|
|
289
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
290
|
+
);
|
|
291
|
+
stats.filesValidated += yamlFiles.length;
|
|
292
|
+
stats.schemasUsed.add(schemaFile);
|
|
293
|
+
|
|
294
|
+
if (!result.valid) {
|
|
295
|
+
allErrors.push(...result.errors);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return createValidationResult(allErrors.length === 0, allErrors, warnings);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Validate referential integrity (skill/behaviour references exist)
|
|
304
|
+
* This supplements schema validation with cross-file reference checks
|
|
305
|
+
* @param {Object} data - Loaded data object
|
|
306
|
+
* @param {Array} data.skills - Skills
|
|
307
|
+
* @param {Array} data.behaviours - Behaviours
|
|
308
|
+
* @param {Array} data.disciplines - Disciplines
|
|
309
|
+
* @param {Array} data.drivers - Drivers
|
|
310
|
+
* @param {Array} data.capabilities - Capabilities
|
|
311
|
+
* @returns {{valid: boolean, errors: Array, warnings: Array}}
|
|
312
|
+
*/
|
|
313
|
+
export function validateReferentialIntegrity(data) {
|
|
314
|
+
const errors = [];
|
|
315
|
+
const warnings = [];
|
|
316
|
+
|
|
317
|
+
const skillIds = new Set((data.skills || []).map((s) => s.id));
|
|
318
|
+
const behaviourIds = new Set((data.behaviours || []).map((b) => b.id));
|
|
319
|
+
const capabilityIds = new Set((data.capabilities || []).map((c) => c.id));
|
|
320
|
+
|
|
321
|
+
// Validate discipline skill references
|
|
322
|
+
for (const discipline of data.disciplines || []) {
|
|
323
|
+
const allSkillRefs = [
|
|
324
|
+
...(discipline.coreSkills || []),
|
|
325
|
+
...(discipline.supportingSkills || []),
|
|
326
|
+
...(discipline.broadSkills || []),
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
for (const skillId of allSkillRefs) {
|
|
330
|
+
if (!skillIds.has(skillId)) {
|
|
331
|
+
errors.push(
|
|
332
|
+
createError(
|
|
333
|
+
"INVALID_REFERENCE",
|
|
334
|
+
`Discipline '${discipline.id}' references unknown skill '${skillId}'`,
|
|
335
|
+
`disciplines/${discipline.id}`,
|
|
336
|
+
),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Validate behaviour modifier references
|
|
342
|
+
for (const behaviourId of Object.keys(
|
|
343
|
+
discipline.behaviourModifiers || {},
|
|
344
|
+
)) {
|
|
345
|
+
if (!behaviourIds.has(behaviourId)) {
|
|
346
|
+
errors.push(
|
|
347
|
+
createError(
|
|
348
|
+
"INVALID_REFERENCE",
|
|
349
|
+
`Discipline '${discipline.id}' references unknown behaviour '${behaviourId}'`,
|
|
350
|
+
`disciplines/${discipline.id}`,
|
|
351
|
+
),
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Validate track skill modifier references (should reference capabilities, not skills)
|
|
358
|
+
for (const track of data.tracks || []) {
|
|
359
|
+
for (const capabilityId of Object.keys(track.skillModifiers || {})) {
|
|
360
|
+
if (!capabilityIds.has(capabilityId)) {
|
|
361
|
+
errors.push(
|
|
362
|
+
createError(
|
|
363
|
+
"INVALID_REFERENCE",
|
|
364
|
+
`Track '${track.id}' references unknown capability '${capabilityId}'`,
|
|
365
|
+
`tracks/${track.id}`,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Validate behaviour modifier references
|
|
372
|
+
for (const behaviourId of Object.keys(track.behaviourModifiers || {})) {
|
|
373
|
+
if (!behaviourIds.has(behaviourId)) {
|
|
374
|
+
errors.push(
|
|
375
|
+
createError(
|
|
376
|
+
"INVALID_REFERENCE",
|
|
377
|
+
`Track '${track.id}' references unknown behaviour '${behaviourId}'`,
|
|
378
|
+
`tracks/${track.id}`,
|
|
379
|
+
),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Validate driver skill/behaviour references
|
|
386
|
+
for (const driver of data.drivers || []) {
|
|
387
|
+
for (const skillId of driver.contributingSkills || []) {
|
|
388
|
+
if (!skillIds.has(skillId)) {
|
|
389
|
+
errors.push(
|
|
390
|
+
createError(
|
|
391
|
+
"INVALID_REFERENCE",
|
|
392
|
+
`Driver '${driver.id}' references unknown skill '${skillId}'`,
|
|
393
|
+
`drivers`,
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const behaviourId of driver.contributingBehaviours || []) {
|
|
400
|
+
if (!behaviourIds.has(behaviourId)) {
|
|
401
|
+
errors.push(
|
|
402
|
+
createError(
|
|
403
|
+
"INVALID_REFERENCE",
|
|
404
|
+
`Driver '${driver.id}' references unknown behaviour '${behaviourId}'`,
|
|
405
|
+
`drivers`,
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return createValidationResult(errors.length === 0, errors, warnings);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Run full validation: schema validation + referential integrity
|
|
417
|
+
* @param {string} dataDir - Path to the data directory
|
|
418
|
+
* @param {Object} [loadedData] - Pre-loaded data (if available, skips schema validation stats gathering)
|
|
419
|
+
* @returns {Promise<{valid: boolean, errors: Array, warnings: Array}>}
|
|
420
|
+
*/
|
|
421
|
+
export async function runSchemaValidation(dataDir, loadedData) {
|
|
422
|
+
const allErrors = [];
|
|
423
|
+
const allWarnings = [];
|
|
424
|
+
|
|
425
|
+
// Run schema validation
|
|
426
|
+
const schemaResult = await validateDataDirectory(dataDir);
|
|
427
|
+
allErrors.push(...schemaResult.errors);
|
|
428
|
+
allWarnings.push(...schemaResult.warnings);
|
|
429
|
+
|
|
430
|
+
// If we have loaded data, also check referential integrity
|
|
431
|
+
if (loadedData) {
|
|
432
|
+
const refResult = validateReferentialIntegrity(loadedData);
|
|
433
|
+
allErrors.push(...refResult.errors);
|
|
434
|
+
allWarnings.push(...refResult.warnings);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return createValidationResult(allErrors.length === 0, allErrors, allWarnings);
|
|
438
|
+
}
|