@unrdf/kgc-runtime 26.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/IMPLEMENTATION_SUMMARY.json +150 -0
  2. package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
  3. package/README.md +98 -0
  4. package/TRANSACTION_IMPLEMENTATION.json +119 -0
  5. package/capability-map.md +93 -0
  6. package/docs/api-stability.md +269 -0
  7. package/docs/extensions/plugin-development.md +382 -0
  8. package/package.json +40 -0
  9. package/plugins/registry.json +35 -0
  10. package/src/admission-gate.mjs +414 -0
  11. package/src/api-version.mjs +373 -0
  12. package/src/atomic-admission.mjs +310 -0
  13. package/src/bounds.mjs +289 -0
  14. package/src/bulkhead-manager.mjs +280 -0
  15. package/src/capsule.mjs +524 -0
  16. package/src/crdt.mjs +361 -0
  17. package/src/enhanced-bounds.mjs +614 -0
  18. package/src/executor.mjs +73 -0
  19. package/src/freeze-restore.mjs +521 -0
  20. package/src/index.mjs +62 -0
  21. package/src/materialized-views.mjs +371 -0
  22. package/src/merge.mjs +472 -0
  23. package/src/plugin-isolation.mjs +392 -0
  24. package/src/plugin-manager.mjs +441 -0
  25. package/src/projections-api.mjs +336 -0
  26. package/src/projections-cli.mjs +238 -0
  27. package/src/projections-docs.mjs +300 -0
  28. package/src/projections-ide.mjs +278 -0
  29. package/src/receipt.mjs +340 -0
  30. package/src/rollback.mjs +258 -0
  31. package/src/saga-orchestrator.mjs +355 -0
  32. package/src/schemas.mjs +1330 -0
  33. package/src/storage-optimization.mjs +359 -0
  34. package/src/tool-registry.mjs +272 -0
  35. package/src/transaction.mjs +466 -0
  36. package/src/validators.mjs +485 -0
  37. package/src/work-item.mjs +449 -0
  38. package/templates/plugin-template/README.md +58 -0
  39. package/templates/plugin-template/index.mjs +162 -0
  40. package/templates/plugin-template/plugin.json +19 -0
  41. package/test/admission-gate.test.mjs +583 -0
  42. package/test/api-version.test.mjs +74 -0
  43. package/test/atomic-admission.test.mjs +155 -0
  44. package/test/bounds.test.mjs +341 -0
  45. package/test/bulkhead-manager.test.mjs +236 -0
  46. package/test/capsule.test.mjs +625 -0
  47. package/test/crdt.test.mjs +215 -0
  48. package/test/enhanced-bounds.test.mjs +487 -0
  49. package/test/freeze-restore.test.mjs +472 -0
  50. package/test/materialized-views.test.mjs +243 -0
  51. package/test/merge.test.mjs +665 -0
  52. package/test/plugin-isolation.test.mjs +109 -0
  53. package/test/plugin-manager.test.mjs +208 -0
  54. package/test/projections-api.test.mjs +293 -0
  55. package/test/projections-cli.test.mjs +204 -0
  56. package/test/projections-docs.test.mjs +173 -0
  57. package/test/projections-ide.test.mjs +230 -0
  58. package/test/receipt.test.mjs +295 -0
  59. package/test/rollback.test.mjs +132 -0
  60. package/test/saga-orchestrator.test.mjs +279 -0
  61. package/test/schemas.test.mjs +716 -0
  62. package/test/storage-optimization.test.mjs +503 -0
  63. package/test/tool-registry.test.mjs +341 -0
  64. package/test/transaction.test.mjs +189 -0
  65. package/test/validators.test.mjs +463 -0
  66. package/test/work-item.test.mjs +548 -0
  67. package/test/work-item.test.mjs.bak +548 -0
  68. package/var/kgc/test-atomic-log.json +519 -0
  69. package/var/kgc/test-cascading-log.json +145 -0
  70. package/vitest.config.mjs +18 -0
@@ -0,0 +1,359 @@
1
+ /**
2
+ * @fileoverview Storage optimization utilities for KGC runtime
3
+ * Provides compression, garbage collection, and archival capabilities
4
+ */
5
+
6
+ import { promises as fs } from 'fs';
7
+ import { createGzip, createGunzip } from 'zlib';
8
+ import { pipeline } from 'stream/promises';
9
+ import { createReadStream, createWriteStream } from 'fs';
10
+ import * as path from 'path';
11
+
12
+ /**
13
+ * Compress file using gzip (level 6)
14
+ * @param {string} inputPath - Path to input file
15
+ * @param {string} outputPath - Path to output file (will be .gz)
16
+ * @returns {Promise<{original_size: number, compressed_size: number}>} Size metadata
17
+ */
18
+ export async function compressFile(inputPath, outputPath) {
19
+ const stats = await fs.stat(inputPath);
20
+ const originalSize = stats.size;
21
+
22
+ const gzip = createGzip({ level: 6 });
23
+ const source = createReadStream(inputPath);
24
+ const destination = createWriteStream(outputPath);
25
+
26
+ await pipeline(source, gzip, destination);
27
+
28
+ const compressedStats = await fs.stat(outputPath);
29
+ const compressedSize = compressedStats.size;
30
+
31
+ return {
32
+ original_size: originalSize,
33
+ compressed_size: compressedSize,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Decompress gzip file
39
+ * @param {string} inputPath - Path to .gz file
40
+ * @param {string} outputPath - Path to output file
41
+ * @returns {Promise<void>}
42
+ */
43
+ export async function decompressFile(inputPath, outputPath) {
44
+ const gunzip = createGunzip();
45
+ const source = createReadStream(inputPath);
46
+ const destination = createWriteStream(outputPath);
47
+
48
+ await pipeline(source, gunzip, destination);
49
+ }
50
+
51
+ /**
52
+ * Read and decompress gzip file to string
53
+ * @param {string} filePath - Path to .gz file
54
+ * @returns {Promise<string>} Decompressed content
55
+ */
56
+ export async function readCompressed(filePath) {
57
+ const chunks = [];
58
+ const gunzip = createGunzip();
59
+ const source = createReadStream(filePath);
60
+
61
+ await pipeline(
62
+ source,
63
+ gunzip,
64
+ async function* (source) {
65
+ for await (const chunk of source) {
66
+ chunks.push(chunk);
67
+ }
68
+ }
69
+ );
70
+
71
+ return Buffer.concat(chunks).toString('utf-8');
72
+ }
73
+
74
+ /**
75
+ * Garbage collection configuration
76
+ * @typedef {Object} GCConfig
77
+ * @property {number} maxSnapshots - Maximum number of snapshots to keep
78
+ * @property {number} ttlDays - Time-to-live in days
79
+ */
80
+
81
+ /**
82
+ * Apply garbage collection to snapshots
83
+ * @param {string} snapshotDir - Snapshot directory path
84
+ * @param {GCConfig} config - GC configuration
85
+ * @returns {Promise<{deleted: number, kept: number, bytes_freed: number}>} GC results
86
+ */
87
+ export async function garbageCollectSnapshots(
88
+ snapshotDir,
89
+ config = { maxSnapshots: 100, ttlDays: 30 }
90
+ ) {
91
+ try {
92
+ await fs.access(snapshotDir);
93
+ } catch {
94
+ return { deleted: 0, kept: 0, bytes_freed: 0 };
95
+ }
96
+
97
+ const entries = await fs.readdir(snapshotDir, { withFileTypes: true });
98
+ const snapshotDirs = entries
99
+ .filter((entry) => entry.isDirectory())
100
+ .map((entry) => entry.name);
101
+
102
+ const snapshots = [];
103
+ for (const dirName of snapshotDirs) {
104
+ try {
105
+ const manifestPath = path.join(snapshotDir, dirName, 'manifest.json');
106
+ const manifestData = await fs.readFile(manifestPath, 'utf-8');
107
+ const manifest = JSON.parse(manifestData);
108
+
109
+ snapshots.push({
110
+ dirName,
111
+ path: path.join(snapshotDir, dirName),
112
+ manifest,
113
+ timestamp_ns: BigInt(manifest.timestamp_ns),
114
+ created_at: new Date(manifest.created_at),
115
+ });
116
+ } catch {
117
+ // Skip invalid snapshots
118
+ }
119
+ }
120
+
121
+ // Sort by timestamp (newest first)
122
+ snapshots.sort((a, b) => (a.timestamp_ns > b.timestamp_ns ? -1 : 1));
123
+
124
+ const now = new Date();
125
+ const ttlMs = config.ttlDays * 24 * 60 * 60 * 1000;
126
+ const cutoffDate = new Date(now.getTime() - ttlMs);
127
+
128
+ let deleted = 0;
129
+ let bytesFreed = 0;
130
+
131
+ for (let i = 0; i < snapshots.length; i++) {
132
+ const snapshot = snapshots[i];
133
+ const shouldDelete =
134
+ i >= config.maxSnapshots || snapshot.created_at < cutoffDate;
135
+
136
+ if (shouldDelete) {
137
+ try {
138
+ // Calculate size before deletion
139
+ const size = await getDirectorySize(snapshot.path);
140
+ await fs.rm(snapshot.path, { recursive: true, force: true });
141
+ deleted++;
142
+ bytesFreed += size;
143
+ } catch (error) {
144
+ // Continue if deletion fails
145
+ if (typeof console !== 'undefined' && console.warn) {
146
+ console.warn(
147
+ `[GC] Failed to delete snapshot ${snapshot.dirName}: ${error.message}`
148
+ );
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ return {
155
+ deleted,
156
+ kept: snapshots.length - deleted,
157
+ bytes_freed: bytesFreed,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Get total size of directory
163
+ * @param {string} dirPath - Directory path
164
+ * @returns {Promise<number>} Total bytes
165
+ * @private
166
+ */
167
+ async function getDirectorySize(dirPath) {
168
+ let totalSize = 0;
169
+
170
+ async function traverse(currentPath) {
171
+ const entries = await fs.readdir(currentPath, { withFileTypes: true });
172
+
173
+ for (const entry of entries) {
174
+ const fullPath = path.join(currentPath, entry.name);
175
+
176
+ if (entry.isDirectory()) {
177
+ await traverse(fullPath);
178
+ } else {
179
+ const stats = await fs.stat(fullPath);
180
+ totalSize += stats.size;
181
+ }
182
+ }
183
+ }
184
+
185
+ await traverse(dirPath);
186
+ return totalSize;
187
+ }
188
+
189
+ /**
190
+ * Receipt archival configuration
191
+ * @typedef {Object} ArchivalConfig
192
+ * @property {number} keepRecent - Number of recent receipts to keep
193
+ * @property {number} keepDays - Days to keep receipts in main store
194
+ */
195
+
196
+ /**
197
+ * Archive old receipts
198
+ * @param {string} receiptDir - Receipt directory path
199
+ * @param {string} archiveDir - Archive directory path
200
+ * @param {ArchivalConfig} config - Archival configuration
201
+ * @returns {Promise<{archived: number, kept: number}>} Archival results
202
+ */
203
+ export async function archiveReceipts(
204
+ receiptDir,
205
+ archiveDir,
206
+ config = { keepRecent: 1000, keepDays: 7 }
207
+ ) {
208
+ try {
209
+ await fs.access(receiptDir);
210
+ } catch {
211
+ return { archived: 0, kept: 0 };
212
+ }
213
+
214
+ await fs.mkdir(archiveDir, { recursive: true });
215
+
216
+ const files = await fs.readdir(receiptDir);
217
+ const receiptFiles = files.filter((f) => f.startsWith('receipt-') && f.endsWith('.json'));
218
+
219
+ const receipts = [];
220
+ for (const file of receiptFiles) {
221
+ try {
222
+ const filePath = path.join(receiptDir, file);
223
+ const stats = await fs.stat(filePath);
224
+ const data = await fs.readFile(filePath, 'utf-8');
225
+ const receipt = JSON.parse(data);
226
+
227
+ receipts.push({
228
+ file,
229
+ path: filePath,
230
+ receipt,
231
+ timestamp: new Date(receipt.timestamp),
232
+ modified: stats.mtime,
233
+ });
234
+ } catch {
235
+ // Skip invalid files
236
+ }
237
+ }
238
+
239
+ // Sort by timestamp (newest first)
240
+ receipts.sort((a, b) => b.timestamp - a.timestamp);
241
+
242
+ const now = new Date();
243
+ const keepMs = config.keepDays * 24 * 60 * 60 * 1000;
244
+ const cutoffDate = new Date(now.getTime() - keepMs);
245
+
246
+ let archived = 0;
247
+
248
+ for (let i = 0; i < receipts.length; i++) {
249
+ const receipt = receipts[i];
250
+ const shouldArchive = i >= config.keepRecent || receipt.timestamp < cutoffDate;
251
+
252
+ if (shouldArchive) {
253
+ try {
254
+ const archivePath = path.join(archiveDir, receipt.file);
255
+ await fs.rename(receipt.path, archivePath);
256
+ archived++;
257
+ } catch (error) {
258
+ // Continue if archival fails
259
+ if (typeof console !== 'undefined' && console.warn) {
260
+ console.warn(
261
+ `[Archive] Failed to archive receipt ${receipt.file}: ${error.message}`
262
+ );
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ return {
269
+ archived,
270
+ kept: receipts.length - archived,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Compute delta between two objects (for incremental snapshots)
276
+ * @param {Object} previous - Previous state
277
+ * @param {Object} current - Current state
278
+ * @returns {Object} Delta object with changes
279
+ */
280
+ export function computeDelta(previous, current) {
281
+ const delta = {
282
+ added: {},
283
+ modified: {},
284
+ deleted: {},
285
+ };
286
+
287
+ const prevStr = JSON.stringify(previous);
288
+ const currStr = JSON.stringify(current);
289
+
290
+ // If identical, return empty delta
291
+ if (prevStr === currStr) {
292
+ return delta;
293
+ }
294
+
295
+ // Simple delta: store full current state if different
296
+ // Phase 1: basic implementation (can be optimized later)
297
+ const prevKeys = new Set(Object.keys(previous || {}));
298
+ const currKeys = new Set(Object.keys(current || {}));
299
+
300
+ // Added keys
301
+ for (const key of currKeys) {
302
+ if (!prevKeys.has(key)) {
303
+ delta.added[key] = current[key];
304
+ }
305
+ }
306
+
307
+ // Modified keys
308
+ for (const key of currKeys) {
309
+ if (prevKeys.has(key)) {
310
+ const prevValue = JSON.stringify(previous[key]);
311
+ const currValue = JSON.stringify(current[key]);
312
+ if (prevValue !== currValue) {
313
+ delta.modified[key] = current[key];
314
+ }
315
+ }
316
+ }
317
+
318
+ // Deleted keys
319
+ for (const key of prevKeys) {
320
+ if (!currKeys.has(key)) {
321
+ delta.deleted[key] = true;
322
+ }
323
+ }
324
+
325
+ return delta;
326
+ }
327
+
328
+ /**
329
+ * Apply delta to base state
330
+ * @param {Object} base - Base state
331
+ * @param {Object} delta - Delta to apply
332
+ * @returns {Object} Reconstructed state
333
+ */
334
+ export function applyDelta(base, delta) {
335
+ const result = { ...base };
336
+
337
+ // Apply additions
338
+ if (delta.added) {
339
+ for (const [key, value] of Object.entries(delta.added)) {
340
+ result[key] = value;
341
+ }
342
+ }
343
+
344
+ // Apply modifications
345
+ if (delta.modified) {
346
+ for (const [key, value] of Object.entries(delta.modified)) {
347
+ result[key] = value;
348
+ }
349
+ }
350
+
351
+ // Apply deletions
352
+ if (delta.deleted) {
353
+ for (const key of Object.keys(delta.deleted)) {
354
+ delete result[key];
355
+ }
356
+ }
357
+
358
+ return result;
359
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * @file Tool Registry - Central repository for tool manifests
3
+ * @module @unrdf/kgc-runtime/tool-registry
4
+ * @description Manages tool manifests, versioning, and capability queries
5
+ */
6
+
7
+ import { z } from 'zod';
8
+ import { readFileSync } from 'fs';
9
+ import { fileURLToPath } from 'url';
10
+ import { dirname, join } from 'path';
11
+
12
+ /**
13
+ * Tool manifest schema
14
+ */
15
+ const ManifestSchema = z.object({
16
+ name: z.string(),
17
+ version: z.string(),
18
+ description: z.string().optional(),
19
+ schema_in: z.any(), // Will be converted to Zod schema
20
+ schema_out: z.any(), // Will be converted to Zod schema
21
+ capabilities: z.array(z.string()),
22
+ });
23
+
24
+ /**
25
+ * Tool Registry class for managing tool manifests
26
+ */
27
+ export class ToolRegistry {
28
+ /**
29
+ * Create a new ToolRegistry
30
+ * @param {Object} options - Configuration options
31
+ * @param {string} options.registryPath - Path to registry JSON file
32
+ */
33
+ constructor(options = {}) {
34
+ /** @type {Map<string, Object>} */
35
+ this.tools = new Map();
36
+
37
+ /** @type {Map<string, Map<string, Object>>} */
38
+ this.versionedTools = new Map();
39
+
40
+ this.registryPath = options.registryPath;
41
+
42
+ if (this.registryPath) {
43
+ this.loadFromFile(this.registryPath);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Load tool manifests from JSON file
49
+ * @param {string} filePath - Path to registry JSON
50
+ */
51
+ loadFromFile(filePath) {
52
+ try {
53
+ const data = readFileSync(filePath, 'utf-8');
54
+ const registry = JSON.parse(data);
55
+
56
+ if (registry.tools && Array.isArray(registry.tools)) {
57
+ for (const toolData of registry.tools) {
58
+ this.registerTool(toolData);
59
+ }
60
+ }
61
+ } catch (error) {
62
+ throw new Error(`Failed to load registry from ${filePath}: ${error.message}`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Register a tool manifest
68
+ * @param {Object} manifest - Tool manifest
69
+ * @throws {Error} If manifest is invalid
70
+ */
71
+ registerTool(manifest) {
72
+ // Validate manifest structure
73
+ ManifestSchema.parse(manifest);
74
+
75
+ const { name, version } = manifest;
76
+
77
+ // Convert schema definitions to Zod schemas if they're objects
78
+ const processedManifest = {
79
+ ...manifest,
80
+ schema_in:
81
+ typeof manifest.schema_in === 'object' &&
82
+ !manifest.schema_in._def
83
+ ? this.convertToZodSchema(manifest.schema_in)
84
+ : manifest.schema_in,
85
+ schema_out:
86
+ typeof manifest.schema_out === 'object' &&
87
+ !manifest.schema_out._def
88
+ ? this.convertToZodSchema(manifest.schema_out)
89
+ : manifest.schema_out,
90
+ };
91
+
92
+ // Store latest version by name
93
+ this.tools.set(name, processedManifest);
94
+
95
+ // Store versioned tool
96
+ if (!this.versionedTools.has(name)) {
97
+ this.versionedTools.set(name, new Map());
98
+ }
99
+ this.versionedTools.get(name).set(version, processedManifest);
100
+ }
101
+
102
+ /**
103
+ * Convert schema definition to Zod schema
104
+ * @param {Object} schemaDef - Schema definition
105
+ * @returns {z.ZodSchema} Zod schema
106
+ * @private
107
+ */
108
+ convertToZodSchema(schemaDef) {
109
+ if (!schemaDef.type) {
110
+ return z.any();
111
+ }
112
+
113
+ switch (schemaDef.type) {
114
+ case 'object': {
115
+ const shape = {};
116
+ if (schemaDef.properties) {
117
+ for (const [key, propDef] of Object.entries(
118
+ schemaDef.properties,
119
+ )) {
120
+ let fieldSchema = this.convertToZodSchema(propDef);
121
+
122
+ // Handle optional fields
123
+ if (
124
+ schemaDef.required &&
125
+ !schemaDef.required.includes(key)
126
+ ) {
127
+ fieldSchema = fieldSchema.optional();
128
+ }
129
+
130
+ shape[key] = fieldSchema;
131
+ }
132
+ }
133
+ return z.object(shape);
134
+ }
135
+
136
+ case 'string':
137
+ return z.string();
138
+
139
+ case 'number':
140
+ return z.number();
141
+
142
+ case 'boolean':
143
+ return z.boolean();
144
+
145
+ case 'array':
146
+ return z.array(
147
+ schemaDef.items
148
+ ? this.convertToZodSchema(schemaDef.items)
149
+ : z.any(),
150
+ );
151
+
152
+ case 'null':
153
+ return z.null();
154
+
155
+ default:
156
+ return z.any();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Get tool manifest by name (latest version)
162
+ * @param {string} name - Tool name
163
+ * @returns {Object|null} Tool manifest or null if not found
164
+ */
165
+ getTool(name) {
166
+ return this.tools.get(name) || null;
167
+ }
168
+
169
+ /**
170
+ * Get tool manifest by name and version
171
+ * @param {string} name - Tool name
172
+ * @param {string} version - Tool version
173
+ * @returns {Object|null} Tool manifest or null if not found
174
+ */
175
+ getToolVersion(name, version) {
176
+ const versions = this.versionedTools.get(name);
177
+ if (!versions) return null;
178
+ return versions.get(version) || null;
179
+ }
180
+
181
+ /**
182
+ * Get all tool manifests
183
+ * @returns {Array<Object>} Array of all tool manifests
184
+ */
185
+ getAllTools() {
186
+ return Array.from(this.tools.values());
187
+ }
188
+
189
+ /**
190
+ * Get all versions of a tool
191
+ * @param {string} name - Tool name
192
+ * @returns {Array<Object>} Array of tool manifests for all versions
193
+ */
194
+ getAllVersions(name) {
195
+ const versions = this.versionedTools.get(name);
196
+ if (!versions) return [];
197
+ return Array.from(versions.values());
198
+ }
199
+
200
+ /**
201
+ * Query tools by capability
202
+ * @param {string} capability - Capability to search for
203
+ * @returns {Array<Object>} Array of tools with the capability
204
+ */
205
+ getToolsByCapability(capability) {
206
+ return this.getAllTools().filter((tool) =>
207
+ tool.capabilities.includes(capability),
208
+ );
209
+ }
210
+
211
+ /**
212
+ * Check if tool supports capability
213
+ * @param {string} toolName - Tool name
214
+ * @param {string} capability - Capability to check
215
+ * @returns {boolean} True if tool supports capability
216
+ */
217
+ hasCapability(toolName, capability) {
218
+ const tool = this.getTool(toolName);
219
+ if (!tool) return false;
220
+ return tool.capabilities.includes(capability);
221
+ }
222
+
223
+ /**
224
+ * Validate tool output against registered schema
225
+ * @param {string} toolName - Tool name
226
+ * @param {*} output - Output to validate
227
+ * @returns {boolean} True if valid
228
+ */
229
+ validateOutput(toolName, output) {
230
+ const tool = this.getTool(toolName);
231
+ if (!tool) {
232
+ throw new Error(`Tool ${toolName} not found in registry`);
233
+ }
234
+
235
+ try {
236
+ tool.schema_out.parse(output);
237
+ return true;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Get registry statistics
245
+ * @returns {Object} Statistics about the registry
246
+ */
247
+ getStats() {
248
+ const tools = this.getAllTools();
249
+ const capabilities = new Set();
250
+
251
+ for (const tool of tools) {
252
+ for (const cap of tool.capabilities) {
253
+ capabilities.add(cap);
254
+ }
255
+ }
256
+
257
+ return {
258
+ total_tools: tools.length,
259
+ unique_capabilities: capabilities.size,
260
+ capabilities: Array.from(capabilities),
261
+ };
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Create a ToolRegistry instance with default registry
267
+ * @param {string} registryPath - Optional path to registry file
268
+ * @returns {ToolRegistry} New ToolRegistry instance
269
+ */
270
+ export function createRegistry(registryPath) {
271
+ return new ToolRegistry({ registryPath });
272
+ }