@principal-ai/principal-view-core 0.28.1 → 0.28.3

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 (49) hide show
  1. package/dist/events/ScopeEventsValidator.d.ts +3 -0
  2. package/dist/events/ScopeEventsValidator.d.ts.map +1 -1
  3. package/dist/events/ScopeEventsValidator.js +5 -4
  4. package/dist/events/ScopeEventsValidator.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/node.d.ts +2 -2
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/node.js +4 -2
  12. package/dist/node.js.map +1 -1
  13. package/dist/scopes/ScopePathIndex.d.ts +56 -0
  14. package/dist/scopes/ScopePathIndex.d.ts.map +1 -0
  15. package/dist/scopes/ScopePathIndex.js +67 -0
  16. package/dist/scopes/ScopePathIndex.js.map +1 -0
  17. package/dist/scopes/ScopesCanvasValidator.d.ts.map +1 -1
  18. package/dist/scopes/ScopesCanvasValidator.js +64 -0
  19. package/dist/scopes/ScopesCanvasValidator.js.map +1 -1
  20. package/dist/scopes/index.d.ts +2 -0
  21. package/dist/scopes/index.d.ts.map +1 -1
  22. package/dist/scopes/index.js +5 -1
  23. package/dist/scopes/index.js.map +1 -1
  24. package/dist/scopes/validateScopeNamespaceNesting.d.ts +38 -0
  25. package/dist/scopes/validateScopeNamespaceNesting.d.ts.map +1 -0
  26. package/dist/scopes/validateScopeNamespaceNesting.js +69 -0
  27. package/dist/scopes/validateScopeNamespaceNesting.js.map +1 -0
  28. package/dist/types/canvas.d.ts +15 -0
  29. package/dist/types/canvas.d.ts.map +1 -1
  30. package/dist/types/canvas.js.map +1 -1
  31. package/dist/workflow/validator.d.ts +4 -1
  32. package/dist/workflow/validator.d.ts.map +1 -1
  33. package/dist/workflow/validator.js +14 -12
  34. package/dist/workflow/validator.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/events/ScopeEventsValidator.test.ts +173 -0
  37. package/src/events/ScopeEventsValidator.ts +5 -4
  38. package/src/index.ts +6 -0
  39. package/src/node.ts +6 -0
  40. package/src/scopes/ScopePathIndex.test.ts +94 -0
  41. package/src/scopes/ScopePathIndex.ts +89 -0
  42. package/src/scopes/ScopesCanvasValidator.test.ts +126 -0
  43. package/src/scopes/ScopesCanvasValidator.ts +68 -0
  44. package/src/scopes/index.ts +12 -0
  45. package/src/scopes/validateScopeNamespaceNesting.test.ts +127 -0
  46. package/src/scopes/validateScopeNamespaceNesting.ts +88 -0
  47. package/src/types/canvas.ts +15 -0
  48. package/src/workflow/__tests__/validator.test.ts +120 -1
  49. package/src/workflow/validator.ts +14 -12
@@ -7,6 +7,7 @@ import type { WorkflowTemplate, WorkflowValidationContext } from '../validator';
7
7
  import type { ExtendedCanvas } from '../../types/canvas';
8
8
  import type { ComponentLibrary } from '../../types/library';
9
9
  import { EventRegistry } from '../../registry/EventRegistry';
10
+ import { NodeFileSystemAdapter } from '@principal-ai/repository-abstraction/node';
10
11
  import { mkdtempSync, writeFileSync, rmSync } from 'fs';
11
12
  import { join } from 'path';
12
13
  import { tmpdir } from 'os';
@@ -16,7 +17,7 @@ describe('WorkflowValidator', () => {
16
17
  let tempDir: string;
17
18
 
18
19
  beforeEach(() => {
19
- validator = new WorkflowValidator();
20
+ validator = new WorkflowValidator(new NodeFileSystemAdapter());
20
21
  // Create a temporary directory for test files
21
22
  tempDir = mkdtempSync(join(tmpdir(), 'workflow-validator-test-'));
22
23
  });
@@ -303,6 +304,124 @@ describe('WorkflowValidator', () => {
303
304
  });
304
305
  });
305
306
 
307
+ // ============================================================================
308
+ // Implementation Files Existence Tests (workflow-files-exist)
309
+ // ============================================================================
310
+
311
+ describe('workflow-files-exist', () => {
312
+ // Create the referenced canvas so unrelated canvas-exists violations stay silent.
313
+ function setupCanvas(): string {
314
+ const canvasPath = join(tempDir, 'test.otel.canvas');
315
+ writeFileSync(canvasPath, JSON.stringify(createValidCanvas()));
316
+ writeFileSync(join(tempDir, 'test.md'), '# Test');
317
+ return canvasPath;
318
+ }
319
+
320
+ it('should pass when status is implemented and all files exist', async () => {
321
+ const canvasPath = setupCanvas();
322
+ writeFileSync(join(tempDir, 'impl.ts'), '// impl');
323
+
324
+ const context = createContext(
325
+ { status: 'implemented', scope: 'test-scope', files: ['impl.ts'] },
326
+ { canvasPath }
327
+ );
328
+ const result = await validator.validate(context);
329
+
330
+ const fileViolations = result.violations.filter(
331
+ (v) => v.ruleId === 'workflow-files-exist'
332
+ );
333
+ expect(fileViolations).toHaveLength(0);
334
+ });
335
+
336
+ it('should flag each missing file when status is implemented', async () => {
337
+ const canvasPath = setupCanvas();
338
+
339
+ const context = createContext(
340
+ {
341
+ status: 'implemented',
342
+ scope: 'test-scope',
343
+ files: ['missing-one.ts', 'missing-two.ts'],
344
+ },
345
+ { canvasPath }
346
+ );
347
+ const result = await validator.validate(context);
348
+
349
+ const fileViolations = result.violations.filter(
350
+ (v) => v.ruleId === 'workflow-files-exist'
351
+ );
352
+ expect(fileViolations).toHaveLength(2);
353
+ expect(fileViolations).toContainEqual(
354
+ expect.objectContaining({
355
+ ruleId: 'workflow-files-exist',
356
+ severity: 'error',
357
+ path: 'files',
358
+ message: expect.stringContaining('missing-one.ts'),
359
+ })
360
+ );
361
+ expect(fileViolations).toContainEqual(
362
+ expect.objectContaining({
363
+ ruleId: 'workflow-files-exist',
364
+ message: expect.stringContaining('missing-two.ts'),
365
+ })
366
+ );
367
+ });
368
+
369
+ it('should flag only the missing files when some exist and some do not', async () => {
370
+ const canvasPath = setupCanvas();
371
+ writeFileSync(join(tempDir, 'present.ts'), '// present');
372
+
373
+ const context = createContext(
374
+ {
375
+ status: 'implemented',
376
+ scope: 'test-scope',
377
+ files: ['present.ts', 'absent.ts'],
378
+ },
379
+ { canvasPath }
380
+ );
381
+ const result = await validator.validate(context);
382
+
383
+ const fileViolations = result.violations.filter(
384
+ (v) => v.ruleId === 'workflow-files-exist'
385
+ );
386
+ expect(fileViolations).toHaveLength(1);
387
+ expect(fileViolations[0].message).toContain('absent.ts');
388
+ });
389
+
390
+ it('should not check file existence when status is draft', async () => {
391
+ const canvasPath = setupCanvas();
392
+
393
+ const context = createContext(
394
+ { status: 'draft', files: ['does-not-exist.ts'] },
395
+ { canvasPath }
396
+ );
397
+ const result = await validator.validate(context);
398
+
399
+ const fileViolations = result.violations.filter(
400
+ (v) => v.ruleId === 'workflow-files-exist'
401
+ );
402
+ expect(fileViolations).toHaveLength(0);
403
+ });
404
+
405
+ it('should not check file existence when status is approved', async () => {
406
+ const canvasPath = setupCanvas();
407
+
408
+ const context = createContext(
409
+ {
410
+ status: 'approved',
411
+ scope: 'test-scope',
412
+ files: ['does-not-exist.ts'],
413
+ },
414
+ { canvasPath }
415
+ );
416
+ const result = await validator.validate(context);
417
+
418
+ const fileViolations = result.violations.filter(
419
+ (v) => v.ruleId === 'workflow-files-exist'
420
+ );
421
+ expect(fileViolations).toHaveLength(0);
422
+ });
423
+ });
424
+
306
425
  // ============================================================================
307
426
  // Scenario Validation Tests
308
427
  // ============================================================================
@@ -3,11 +3,11 @@
3
3
  * Validates .workflow.json files against their corresponding .otel.canvas files
4
4
  */
5
5
 
6
+ import type { FileSystemAdapter } from '@principal-ai/repository-abstraction';
6
7
  import type { WorkflowTemplate, WorkflowScenario, ScenarioTemplate } from './types';
7
8
  import { getEventTemplateString } from './types';
8
9
  import type { ExtendedCanvas, OtelEventNode } from '../types/canvas';
9
10
  import { isOtelEventNode } from '../types/canvas';
10
- import { existsSync, readFileSync } from 'fs';
11
11
  import { resolve, basename } from 'path';
12
12
  import type { EventRegistry } from '../registry/EventRegistry';
13
13
  import type { IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer/build/src/trace/internal-types';
@@ -125,6 +125,8 @@ export interface WorkflowValidationResult {
125
125
  // ============================================================================
126
126
 
127
127
  export class WorkflowValidator {
128
+ constructor(private fsAdapter: FileSystemAdapter) {}
129
+
128
130
  /**
129
131
  * Validate a workflow template
130
132
  */
@@ -134,8 +136,8 @@ export class WorkflowValidator {
134
136
  const violations: WorkflowViolation[] = [];
135
137
 
136
138
  // Run all validation rules
137
- violations.push(...this.checkSchema(context));
138
- violations.push(...this.checkCanvasExists(context));
139
+ violations.push(...(await this.checkSchema(context)));
140
+ violations.push(...(await this.checkCanvasExists(context)));
139
141
  violations.push(...this.checkCanvasNodeLabels(context));
140
142
  violations.push(...this.checkCanvasCrossReference(context));
141
143
  violations.push(...this.checkDeprecatedFields(context));
@@ -162,7 +164,7 @@ export class WorkflowValidator {
162
164
 
163
165
  // Check execution data completeness if execution files are provided
164
166
  if (context.executionFiles && context.executionFiles.length > 0) {
165
- violations.push(...this.checkExecutionDataCompleteness(context));
167
+ violations.push(...(await this.checkExecutionDataCompleteness(context)));
166
168
  }
167
169
 
168
170
  return this.aggregateResults(violations);
@@ -221,7 +223,7 @@ export class WorkflowValidator {
221
223
  /**
222
224
  * Check schema validity (required fields, valid values)
223
225
  */
224
- private checkSchema(context: WorkflowValidationContext): WorkflowViolation[] {
226
+ private async checkSchema(context: WorkflowValidationContext): Promise<WorkflowViolation[]> {
225
227
  const violations: WorkflowViolation[] = [];
226
228
  const { workflow, workflowPath } = context;
227
229
 
@@ -426,7 +428,7 @@ export class WorkflowValidator {
426
428
  if (status === 'implemented' && workflow.files && workflow.files.length > 0) {
427
429
  for (const file of workflow.files) {
428
430
  const filePath = resolve(context.basePath, file);
429
- if (!existsSync(filePath)) {
431
+ if (!(await this.fsAdapter.exists(filePath))) {
430
432
  violations.push({
431
433
  ruleId: 'workflow-files-exist',
432
434
  severity: 'error',
@@ -501,7 +503,7 @@ export class WorkflowValidator {
501
503
  /**
502
504
  * Check that the referenced canvas file exists
503
505
  */
504
- private checkCanvasExists(context: WorkflowValidationContext): WorkflowViolation[] {
506
+ private async checkCanvasExists(context: WorkflowValidationContext): Promise<WorkflowViolation[]> {
505
507
  const violations: WorkflowViolation[] = [];
506
508
  const { workflow, workflowPath, basePath, canvasPath } = context;
507
509
 
@@ -513,7 +515,7 @@ export class WorkflowValidator {
513
515
  // Resolve canvas path
514
516
  const resolvedPath = canvasPath || resolve(basePath, workflow.canvas);
515
517
 
516
- if (!existsSync(resolvedPath)) {
518
+ if (!(await this.fsAdapter.exists(resolvedPath))) {
517
519
  violations.push({
518
520
  ruleId: 'workflow-canvas-exists',
519
521
  severity: 'error',
@@ -2302,7 +2304,7 @@ export class WorkflowValidator {
2302
2304
  * Validates that co-located execution files contain the events and attributes
2303
2305
  * that workflow templates reference.
2304
2306
  */
2305
- private checkExecutionDataCompleteness(context: WorkflowValidationContext): WorkflowViolation[] {
2307
+ private async checkExecutionDataCompleteness(context: WorkflowValidationContext): Promise<WorkflowViolation[]> {
2306
2308
  const violations: WorkflowViolation[] = [];
2307
2309
  const { workflow, workflowPath, executionFiles } = context;
2308
2310
 
@@ -2314,7 +2316,7 @@ export class WorkflowValidator {
2314
2316
  const executions: Array<{path: string; data: IExportTraceServiceRequest}> = [];
2315
2317
  for (const execPath of executionFiles) {
2316
2318
  try {
2317
- const content = readFileSync(execPath, 'utf-8');
2319
+ const content = await this.fsAdapter.readFile(execPath);
2318
2320
  const data = JSON.parse(content) as IExportTraceServiceRequest;
2319
2321
  executions.push({ path: execPath, data });
2320
2322
  } catch (error) {
@@ -2730,6 +2732,6 @@ export class WorkflowValidator {
2730
2732
  /**
2731
2733
  * Create a validator instance
2732
2734
  */
2733
- export function createWorkflowValidator(): WorkflowValidator {
2734
- return new WorkflowValidator();
2735
+ export function createWorkflowValidator(fsAdapter: FileSystemAdapter): WorkflowValidator {
2736
+ return new WorkflowValidator(fsAdapter);
2735
2737
  }