@principal-ai/principal-view-cli 0.2.3 → 0.3.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.
@@ -181,100 +181,100 @@ ${chalk.bold('Validation:')}
181
181
  ${chalk.bold.cyan('Execution Format (.otel.json)')}
182
182
  ${chalk.dim('═'.repeat(70))}
183
183
 
184
- Execution files contain captured OTEL spans from test runs or production code.
185
- These files are exported by your test infrastructure and used for visualization
184
+ Execution files use the ${chalk.bold('standard OTLP (OpenTelemetry Protocol) JSON format')}.
185
+ These files are exported by OpenTelemetry SDK and used for visualization
186
186
  and validation against canvas schemas.
187
187
 
188
188
  ${chalk.bold('File Location:')}
189
189
  ${chalk.yellow('__executions__/')}${chalk.dim('<feature-name>.otel.json')}
190
- ${chalk.dim('(auto-generated by test infrastructure)')}
190
+ ${chalk.dim('(auto-generated by test infrastructure with OpenTelemetry SDK)')}
191
191
 
192
192
  ${chalk.bold('IMPORTANT:')} __executions__/ directory must be committed to git!
193
193
 
194
- ${chalk.bold('File Structure:')}
194
+ ${chalk.bold('File Structure (OTLP JSON Format):')}
195
195
  ${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
196
196
  ${chalk.dim('│')} { ${chalk.dim('│')}
197
- ${chalk.dim('│')} ${chalk.green('"exportedAt"')}: "2025-01-21T10:30:00.000Z", ${chalk.dim('// ISO timestamp')} ${chalk.dim('│')}
198
- ${chalk.dim('│')} ${chalk.green('"serviceName"')}: "my-service", ${chalk.dim('// Service name')} ${chalk.dim('│')}
199
- ${chalk.dim('│')} ${chalk.green('"spanCount"')}: 5, ${chalk.dim('// Total spans')} ${chalk.dim('│')}
200
- ${chalk.dim('│')} ${chalk.green('"spans"')}: [ ${chalk.dim('│')}
197
+ ${chalk.dim('│')} ${chalk.green('"resourceSpans"')}: [ ${chalk.dim('// OTLP root structure')} ${chalk.dim('│')}
201
198
  ${chalk.dim('│')} { ${chalk.dim('│')}
202
- ${chalk.dim('│')} ${chalk.yellow('"traceId"')}: "4bf92f3577b34da6...", ${chalk.dim('// 32 hex chars')} ${chalk.dim('│')}
203
- ${chalk.dim('│')} ${chalk.yellow('"spanId"')}: "00f067aa0ba902b7", ${chalk.dim('// 16 hex chars')} ${chalk.dim('│')}
204
- ${chalk.dim('│')} ${chalk.yellow('"parentSpanId"')}: "abc123...", ${chalk.dim('// Parent span (null for root)')} ${chalk.dim('│')}
205
- ${chalk.dim('│')} ${chalk.yellow('"name"')}: "test:feature-name", ${chalk.dim('// Span name')} ${chalk.dim('│')}
206
- ${chalk.dim('│')} ${chalk.yellow('"kind"')}: "INTERNAL", ${chalk.dim('// Span kind')} ${chalk.dim('│')}
207
- ${chalk.dim('│')} ${chalk.yellow('"startTime"')}: 1703548800000, ${chalk.dim('// Unix ms')} ${chalk.dim('│')}
208
- ${chalk.dim('│')} ${chalk.yellow('"endTime"')}: 1703548800050, ${chalk.dim('// Unix ms')} ${chalk.dim('│')}
209
- ${chalk.dim('│')} ${chalk.yellow('"duration"')}: 50, ${chalk.dim('// Duration in ms')} ${chalk.dim('│')}
210
- ${chalk.dim('│')} ${chalk.yellow('"attributes"')}: { ${chalk.dim('// Event attributes')} ${chalk.dim('│')}
211
- ${chalk.dim('│')} "input.size": 42, ${chalk.dim('│')}
212
- ${chalk.dim('│')} "output.success": true ${chalk.dim('│')}
213
- ${chalk.dim('│')} }, ${chalk.dim('│')}
214
- ${chalk.dim('│')} ${chalk.yellow('"status"')}: { ${chalk.dim('│')}
215
- ${chalk.dim('│')} "code": "OK" ${chalk.dim('// OK, ERROR, UNSET')} ${chalk.dim('│')}
216
- ${chalk.dim('│')} }, ${chalk.dim('│')}
217
- ${chalk.dim('│')} ${chalk.yellow('"events"')}: [] ${chalk.dim('// Optional span events')} ${chalk.dim('│')}
199
+ ${chalk.dim('│')} ${chalk.yellow('"resource"')}: { ${chalk.dim('│')}
200
+ ${chalk.dim('│')} ${chalk.cyan('"attributes"')}: [ ${chalk.dim('│')}
201
+ ${chalk.dim('│')} { "key": "service.name", "value": { "stringValue": "..." }} ${chalk.dim('│')}
202
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
203
+ ${chalk.dim('│')} }, ${chalk.dim('│')}
204
+ ${chalk.dim('│')} ${chalk.yellow('"scopeSpans"')}: [ ${chalk.dim('│')}
205
+ ${chalk.dim('│')} { ${chalk.dim('│')}
206
+ ${chalk.dim('│')} ${chalk.cyan('"scope"')}: { "name": "...", "version": "..." }, ${chalk.dim('│')}
207
+ ${chalk.dim('│')} ${chalk.cyan('"spans"')}: [ ${chalk.dim('│')}
208
+ ${chalk.dim('│')} { ${chalk.dim('│')}
209
+ ${chalk.dim('│')} "traceId": "4bf92f3577b34da6...", ${chalk.dim('// 32 hex chars')} ${chalk.dim('│')}
210
+ ${chalk.dim('│')} "spanId": "00f067aa0ba902b7", ${chalk.dim('// 16 hex chars')} ${chalk.dim('│')}
211
+ ${chalk.dim('│')} "name": "test:feature-name", ${chalk.dim('// Span name')} ${chalk.dim('│')}
212
+ ${chalk.dim('│')} "kind": 0, ${chalk.dim('// 0=INTERNAL')} ${chalk.dim('│')}
213
+ ${chalk.dim('│')} "startTimeUnixNano": "1703...", ${chalk.dim('// Unix nanoseconds')} ${chalk.dim('│')}
214
+ ${chalk.dim('│')} "endTimeUnixNano": "1703...", ${chalk.dim('// Unix nanoseconds')} ${chalk.dim('│')}
215
+ ${chalk.dim('│')} "attributes": [ ${chalk.dim('// Key-value pairs')} ${chalk.dim('│')}
216
+ ${chalk.dim('│')} { "key": "input.size", "value": { "intValue": 42 } } ${chalk.dim('│')}
217
+ ${chalk.dim('│')} ], ${chalk.dim('│')}
218
+ ${chalk.dim('│')} "events": [ ${chalk.dim('// Span events')} ${chalk.dim('│')}
219
+ ${chalk.dim('│')} { ${chalk.dim('│')}
220
+ ${chalk.dim('│')} "timeUnixNano": "1703...", ${chalk.dim('│')}
221
+ ${chalk.dim('│')} "name": "validation.started", ${chalk.dim('│')}
222
+ ${chalk.dim('│')} "attributes": [...] ${chalk.dim('│')}
223
+ ${chalk.dim('│')} } ${chalk.dim('│')}
224
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
225
+ ${chalk.dim('│')} } ${chalk.dim('│')}
226
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
227
+ ${chalk.dim('│')} } ${chalk.dim('│')}
228
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
218
229
  ${chalk.dim('│')} } ${chalk.dim('│')}
219
230
  ${chalk.dim('│')} ] ${chalk.dim('│')}
220
231
  ${chalk.dim('│')} } ${chalk.dim('│')}
221
232
  ${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
222
233
 
223
- ${chalk.bold('Field Descriptions:')}
234
+ ${chalk.bold('OTLP Format Details:')}
224
235
 
225
- ${chalk.cyan('exportedAt')} ${chalk.dim('(string, required)')}
226
- ISO 8601 timestamp when the file was exported
236
+ ${chalk.cyan('resourceSpans')} ${chalk.dim('(array, required)')}
237
+ Root array containing resource-grouped spans
227
238
 
228
- ${chalk.cyan('serviceName')} ${chalk.dim('(string, required)')}
229
- Name of the service/test suite that generated these spans
239
+ ${chalk.cyan('resource.attributes')} ${chalk.dim('(array)')}
240
+ Metadata about the service (e.g., service.name)
230
241
 
231
- ${chalk.cyan('spanCount')} ${chalk.dim('(number, required)')}
232
- Total number of spans in this execution
242
+ ${chalk.cyan('scopeSpans')} ${chalk.dim('(array, required)')}
243
+ Instrumentation scope-grouped spans
233
244
 
234
245
  ${chalk.cyan('spans')} ${chalk.dim('(array, required)')}
235
- Array of span objects containing OTEL trace data
236
-
237
- ${chalk.bold('Span Object Fields:')}
238
-
239
- ${chalk.cyan('traceId')} ${chalk.dim('(string, 32 hex chars)')}
240
- Unique identifier for the entire trace
241
-
242
- ${chalk.cyan('spanId')} ${chalk.dim('(string, 16 hex chars)')}
243
- Unique identifier for this specific span
244
-
245
- ${chalk.cyan('parentSpanId')} ${chalk.dim('(string | null)')}
246
- Parent span ID, or null for root span
247
-
248
- ${chalk.cyan('name')} ${chalk.dim('(string)')}
249
- Descriptive name for the operation
250
-
251
- ${chalk.cyan('kind')} ${chalk.dim('(string)')}
252
- Span kind: INTERNAL, CLIENT, SERVER, PRODUCER, CONSUMER
253
-
254
- ${chalk.cyan('startTime')} ${chalk.dim('(number)')}
255
- Unix timestamp in milliseconds when span started
256
-
257
- ${chalk.cyan('endTime')} ${chalk.dim('(number)')}
258
- Unix timestamp in milliseconds when span ended
259
-
260
- ${chalk.cyan('duration')} ${chalk.dim('(number)')}
261
- Duration in milliseconds (endTime - startTime)
262
-
263
- ${chalk.cyan('attributes')} ${chalk.dim('(object)')}
264
- Key-value pairs of event attributes (validated against canvas schema)
265
-
266
- ${chalk.cyan('status')} ${chalk.dim('(object)')}
267
- Status code (OK, ERROR, UNSET) and optional message
268
-
269
- ${chalk.cyan('events')} ${chalk.dim('(array)')}
270
- Optional array of timestamped events within the span
246
+ Array of span objects with OTLP structure
247
+
248
+ ${chalk.bold('Span Fields (OTLP Standard):')}
249
+
250
+ ${chalk.cyan('traceId')} ${chalk.dim('(string)')} - Unique trace identifier (32 hex chars)
251
+ ${chalk.cyan('spanId')} ${chalk.dim('(string)')} - Unique span identifier (16 hex chars)
252
+ ${chalk.cyan('parentSpanId')} ${chalk.dim('(string, optional)')} - Parent span ID
253
+ ${chalk.cyan('name')} ${chalk.dim('(string)')} - Operation name
254
+ ${chalk.cyan('kind')} ${chalk.dim('(number)')} - 0=UNSPECIFIED, 1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER
255
+ ${chalk.cyan('startTimeUnixNano')} ${chalk.dim('(string)')} - Start time in Unix nanoseconds
256
+ ${chalk.cyan('endTimeUnixNano')} ${chalk.dim('(string)')} - End time in Unix nanoseconds
257
+ ${chalk.cyan('attributes')} ${chalk.dim('(array)')} - Key-value pairs: [{ key, value: { stringValue|intValue|... } }]
258
+ ${chalk.cyan('events')} ${chalk.dim('(array)')} - Span events with timestamps and attributes
259
+ ${chalk.cyan('status')} ${chalk.dim('(object)')} - Status: { code: 1=OK, 2=ERROR }
260
+
261
+ ${chalk.bold('Attribute Value Format:')}
262
+ { "stringValue": "..." } ${chalk.dim('// For strings')}
263
+ { "intValue": 42 } ${chalk.dim('// For integers')}
264
+ { "doubleValue": 3.14 } ${chalk.dim('// For floats')}
265
+ { "boolValue": true } ${chalk.dim('// For booleans')}
271
266
 
272
267
  ${chalk.bold('Generation:')}
273
- Set up test infrastructure with OTEL exporters that write to __executions__/
274
- See: docs/guides/adding-opentelemetry-to-tests.md
268
+ Use OpenTelemetry SDK with OTLP JSON exporter
269
+ Example: @opentelemetry/sdk-trace-node + InMemorySpanExporter
270
+ See: tests/otel-setup.ts in the add-skill repository
275
271
 
276
272
  ${chalk.bold('Validation:')}
277
273
  ${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution <file>')}
274
+
275
+ ${chalk.bold('Resources:')}
276
+ OTLP Spec: https://opentelemetry.io/docs/specs/otlp/
277
+ OpenTelemetry JS: https://github.com/open-telemetry/opentelemetry-js
278
278
  `,
279
279
  examples: `
280
280
  ${chalk.bold.cyan('Complete File Examples')}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+pCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAiI/C"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0sCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAiI/C"}
@@ -225,7 +225,7 @@ const ALLOWED_CANVAS_FIELDS = {
225
225
  'name',
226
226
  'description',
227
227
  'otel',
228
- 'events',
228
+ 'event',
229
229
  'shape',
230
230
  'icon',
231
231
  'fill',
@@ -346,7 +346,7 @@ function findSimilarField(field, allowedFields) {
346
346
  * Check if a canvas has OTEL-related features
347
347
  * Returns true if the canvas contains any of:
348
348
  * 1. Nodes with pv.otel extension (kind, category)
349
- * 2. Event schemas (pv.events with validation)
349
+ * 2. Event schema (pv.event with validation)
350
350
  * 3. Canvas scope/audit config (OTEL log routing)
351
351
  * 4. Resource matching for OTEL logs
352
352
  */
@@ -373,8 +373,8 @@ function hasOtelFeatures(canvas) {
373
373
  if (nodePv.otel !== undefined) {
374
374
  return true;
375
375
  }
376
- // Check for event schemas (pv.events)
377
- if (nodePv.events !== undefined) {
376
+ // Check for event schema (pv.event)
377
+ if (nodePv.event !== undefined) {
378
378
  return true;
379
379
  }
380
380
  // Check for resourceMatch (OTEL log routing)
@@ -634,8 +634,19 @@ function validateCanvas(canvas, filePath, library, repositoryPath) {
634
634
  if (nodePv.otel && typeof nodePv.otel === 'object') {
635
635
  checkUnknownFields(nodePv.otel, ALLOWED_CANVAS_FIELDS.nodePvOtel, `${nodePath}.pv.otel`, issues);
636
636
  }
637
+ // For .otel.canvas files: require event field on nodes with pv extension (except groups)
638
+ if (filePath.endsWith('.otel.canvas') && nodeType !== 'group') {
639
+ if (nodePv.event === undefined) {
640
+ issues.push({
641
+ type: 'error',
642
+ message: `Node "${nodeLabel}" in .otel.canvas file must have "pv.event" field`,
643
+ path: `${nodePath}.pv.event`,
644
+ suggestion: 'Add event name, e.g.: "event": "user.login" or "event": "order.created"',
645
+ });
646
+ }
647
+ }
637
648
  // Validate source file references for OTEL event nodes
638
- const hasOtelFeatures = nodePv.otel !== undefined || nodePv.events !== undefined;
649
+ const hasOtelFeatures = nodePv.otel !== undefined || nodePv.event !== undefined;
639
650
  if (hasOtelFeatures) {
640
651
  // OTEL nodes must have at least one source file reference
641
652
  if (!Array.isArray(nodePv.sources) || nodePv.sources.length === 0) {
@@ -646,22 +657,50 @@ function validateCanvas(canvas, filePath, library, repositoryPath) {
646
657
  suggestion: 'Add at least one source file reference, e.g.: "sources": ["src/services/MyService.ts"]',
647
658
  });
648
659
  }
660
+ // For .otel.canvas files: nodes with event must have pv.otel for UI rendering
661
+ if (filePath.endsWith('.otel.canvas') && nodePv.event !== undefined && nodePv.otel === undefined) {
662
+ issues.push({
663
+ type: 'error',
664
+ message: `Node "${nodeLabel}" in .otel.canvas file has event but is missing "pv.otel" field required for UI badges`,
665
+ path: `${nodePath}.pv.otel`,
666
+ suggestion: 'Add OTEL metadata for UI rendering, e.g.: "otel": { "kind": "event", "category": "lifecycle", "isNew": true }',
667
+ });
668
+ }
649
669
  }
650
- // Validate that all source files exist (if repository path is provided)
651
- if (Array.isArray(nodePv.sources) && repositoryPath) {
670
+ // Validate source file paths
671
+ if (Array.isArray(nodePv.sources)) {
652
672
  nodePv.sources.forEach((source, sourceIndex) => {
653
673
  if (typeof source === 'string') {
654
- // Remove glob patterns (* characters) to get the base path
655
- const cleanPath = source.replace(/\*/g, '');
656
- const fullPath = resolve(repositoryPath, cleanPath);
657
- if (!existsSync(fullPath)) {
674
+ // Check for glob patterns
675
+ if (/[*?[\]{}]/.test(source)) {
658
676
  issues.push({
659
677
  type: 'error',
660
- message: `Node "${nodeLabel}" references non-existent source file: ${source}`,
678
+ message: `Node "${nodeLabel}" has glob pattern in sources: ${source}`,
661
679
  path: `${nodePath}.pv.sources[${sourceIndex}]`,
662
- suggestion: `Verify the file path is correct relative to repository root: ${repositoryPath}`,
680
+ suggestion: 'Use exact file paths only. Glob patterns (*, ?, [], {}) are not supported in sources.',
663
681
  });
664
682
  }
683
+ // Check for line number suffix (e.g., "file.ts:123")
684
+ if (/:\d+$/.test(source)) {
685
+ issues.push({
686
+ type: 'error',
687
+ message: `Node "${nodeLabel}" has line number suffix in sources: ${source}`,
688
+ path: `${nodePath}.pv.sources[${sourceIndex}]`,
689
+ suggestion: 'Remove line number suffix. Use exact file paths only (e.g., "src/file.ts" not "src/file.ts:123").',
690
+ });
691
+ }
692
+ // Validate that source file exists (if repository path is provided)
693
+ if (repositoryPath) {
694
+ const fullPath = resolve(repositoryPath, source);
695
+ if (!existsSync(fullPath)) {
696
+ issues.push({
697
+ type: 'error',
698
+ message: `Node "${nodeLabel}" references non-existent source file: ${source}`,
699
+ path: `${nodePath}.pv.sources[${sourceIndex}]`,
700
+ suggestion: `Verify the file path is correct relative to repository root: ${repositoryPath}`,
701
+ });
702
+ }
703
+ }
665
704
  }
666
705
  });
667
706
  }
package/dist/index.cjs CHANGED
@@ -228612,7 +228612,7 @@ var ALLOWED_CANVAS_FIELDS = {
228612
228612
  "name",
228613
228613
  "description",
228614
228614
  "otel",
228615
- "events",
228615
+ "event",
228616
228616
  "shape",
228617
228617
  "icon",
228618
228618
  "fill",
@@ -228735,7 +228735,7 @@ function hasOtelFeatures(canvas) {
228735
228735
  if (nodePv.otel !== void 0) {
228736
228736
  return true;
228737
228737
  }
228738
- if (nodePv.events !== void 0) {
228738
+ if (nodePv.event !== void 0) {
228739
228739
  return true;
228740
228740
  }
228741
228741
  if (nodePv.resourceMatch !== void 0) {
@@ -229019,7 +229019,17 @@ function validateCanvas(canvas, filePath, library, repositoryPath) {
229019
229019
  issues
229020
229020
  );
229021
229021
  }
229022
- const hasOtelFeatures2 = nodePv.otel !== void 0 || nodePv.events !== void 0;
229022
+ if (filePath.endsWith(".otel.canvas") && nodeType !== "group") {
229023
+ if (nodePv.event === void 0) {
229024
+ issues.push({
229025
+ type: "error",
229026
+ message: `Node "${nodeLabel}" in .otel.canvas file must have "pv.event" field`,
229027
+ path: `${nodePath2}.pv.event`,
229028
+ suggestion: 'Add event name, e.g.: "event": "user.login" or "event": "order.created"'
229029
+ });
229030
+ }
229031
+ }
229032
+ const hasOtelFeatures2 = nodePv.otel !== void 0 || nodePv.event !== void 0;
229023
229033
  if (hasOtelFeatures2) {
229024
229034
  if (!Array.isArray(nodePv.sources) || nodePv.sources.length === 0) {
229025
229035
  issues.push({
@@ -229029,20 +229039,45 @@ function validateCanvas(canvas, filePath, library, repositoryPath) {
229029
229039
  suggestion: 'Add at least one source file reference, e.g.: "sources": ["src/services/MyService.ts"]'
229030
229040
  });
229031
229041
  }
229042
+ if (filePath.endsWith(".otel.canvas") && nodePv.event !== void 0 && nodePv.otel === void 0) {
229043
+ issues.push({
229044
+ type: "error",
229045
+ message: `Node "${nodeLabel}" in .otel.canvas file has event but is missing "pv.otel" field required for UI badges`,
229046
+ path: `${nodePath2}.pv.otel`,
229047
+ suggestion: 'Add OTEL metadata for UI rendering, e.g.: "otel": { "kind": "event", "category": "lifecycle", "isNew": true }'
229048
+ });
229049
+ }
229032
229050
  }
229033
- if (Array.isArray(nodePv.sources) && repositoryPath) {
229051
+ if (Array.isArray(nodePv.sources)) {
229034
229052
  nodePv.sources.forEach((source, sourceIndex) => {
229035
229053
  if (typeof source === "string") {
229036
- const cleanPath = source.replace(/\*/g, "");
229037
- const fullPath = (0, import_node_path5.resolve)(repositoryPath, cleanPath);
229038
- if (!(0, import_node_fs4.existsSync)(fullPath)) {
229054
+ if (/[*?[\]{}]/.test(source)) {
229055
+ issues.push({
229056
+ type: "error",
229057
+ message: `Node "${nodeLabel}" has glob pattern in sources: ${source}`,
229058
+ path: `${nodePath2}.pv.sources[${sourceIndex}]`,
229059
+ suggestion: "Use exact file paths only. Glob patterns (*, ?, [], {}) are not supported in sources."
229060
+ });
229061
+ }
229062
+ if (/:\d+$/.test(source)) {
229039
229063
  issues.push({
229040
229064
  type: "error",
229041
- message: `Node "${nodeLabel}" references non-existent source file: ${source}`,
229065
+ message: `Node "${nodeLabel}" has line number suffix in sources: ${source}`,
229042
229066
  path: `${nodePath2}.pv.sources[${sourceIndex}]`,
229043
- suggestion: `Verify the file path is correct relative to repository root: ${repositoryPath}`
229067
+ suggestion: 'Remove line number suffix. Use exact file paths only (e.g., "src/file.ts" not "src/file.ts:123").'
229044
229068
  });
229045
229069
  }
229070
+ if (repositoryPath) {
229071
+ const fullPath = (0, import_node_path5.resolve)(repositoryPath, source);
229072
+ if (!(0, import_node_fs4.existsSync)(fullPath)) {
229073
+ issues.push({
229074
+ type: "error",
229075
+ message: `Node "${nodeLabel}" references non-existent source file: ${source}`,
229076
+ path: `${nodePath2}.pv.sources[${sourceIndex}]`,
229077
+ suggestion: `Verify the file path is correct relative to repository root: ${repositoryPath}`
229078
+ });
229079
+ }
229080
+ }
229046
229081
  }
229047
229082
  });
229048
229083
  }
@@ -241290,100 +241325,100 @@ ${source_default.bold("Validation:")}
241290
241325
  ${source_default.bold.cyan("Execution Format (.otel.json)")}
241291
241326
  ${source_default.dim("\u2550".repeat(70))}
241292
241327
 
241293
- Execution files contain captured OTEL spans from test runs or production code.
241294
- These files are exported by your test infrastructure and used for visualization
241328
+ Execution files use the ${source_default.bold("standard OTLP (OpenTelemetry Protocol) JSON format")}.
241329
+ These files are exported by OpenTelemetry SDK and used for visualization
241295
241330
  and validation against canvas schemas.
241296
241331
 
241297
241332
  ${source_default.bold("File Location:")}
241298
241333
  ${source_default.yellow("__executions__/")}${source_default.dim("<feature-name>.otel.json")}
241299
- ${source_default.dim("(auto-generated by test infrastructure)")}
241334
+ ${source_default.dim("(auto-generated by test infrastructure with OpenTelemetry SDK)")}
241300
241335
 
241301
241336
  ${source_default.bold("IMPORTANT:")} __executions__/ directory must be committed to git!
241302
241337
 
241303
- ${source_default.bold("File Structure:")}
241338
+ ${source_default.bold("File Structure (OTLP JSON Format):")}
241304
241339
  ${source_default.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}
241305
241340
  ${source_default.dim("\u2502")} { ${source_default.dim("\u2502")}
241306
- ${source_default.dim("\u2502")} ${source_default.green('"exportedAt"')}: "2025-01-21T10:30:00.000Z", ${source_default.dim("// ISO timestamp")} ${source_default.dim("\u2502")}
241307
- ${source_default.dim("\u2502")} ${source_default.green('"serviceName"')}: "my-service", ${source_default.dim("// Service name")} ${source_default.dim("\u2502")}
241308
- ${source_default.dim("\u2502")} ${source_default.green('"spanCount"')}: 5, ${source_default.dim("// Total spans")} ${source_default.dim("\u2502")}
241309
- ${source_default.dim("\u2502")} ${source_default.green('"spans"')}: [ ${source_default.dim("\u2502")}
241341
+ ${source_default.dim("\u2502")} ${source_default.green('"resourceSpans"')}: [ ${source_default.dim("// OTLP root structure")} ${source_default.dim("\u2502")}
241310
241342
  ${source_default.dim("\u2502")} { ${source_default.dim("\u2502")}
241311
- ${source_default.dim("\u2502")} ${source_default.yellow('"traceId"')}: "4bf92f3577b34da6...", ${source_default.dim("// 32 hex chars")} ${source_default.dim("\u2502")}
241312
- ${source_default.dim("\u2502")} ${source_default.yellow('"spanId"')}: "00f067aa0ba902b7", ${source_default.dim("// 16 hex chars")} ${source_default.dim("\u2502")}
241313
- ${source_default.dim("\u2502")} ${source_default.yellow('"parentSpanId"')}: "abc123...", ${source_default.dim("// Parent span (null for root)")} ${source_default.dim("\u2502")}
241314
- ${source_default.dim("\u2502")} ${source_default.yellow('"name"')}: "test:feature-name", ${source_default.dim("// Span name")} ${source_default.dim("\u2502")}
241315
- ${source_default.dim("\u2502")} ${source_default.yellow('"kind"')}: "INTERNAL", ${source_default.dim("// Span kind")} ${source_default.dim("\u2502")}
241316
- ${source_default.dim("\u2502")} ${source_default.yellow('"startTime"')}: 1703548800000, ${source_default.dim("// Unix ms")} ${source_default.dim("\u2502")}
241317
- ${source_default.dim("\u2502")} ${source_default.yellow('"endTime"')}: 1703548800050, ${source_default.dim("// Unix ms")} ${source_default.dim("\u2502")}
241318
- ${source_default.dim("\u2502")} ${source_default.yellow('"duration"')}: 50, ${source_default.dim("// Duration in ms")} ${source_default.dim("\u2502")}
241319
- ${source_default.dim("\u2502")} ${source_default.yellow('"attributes"')}: { ${source_default.dim("// Event attributes")} ${source_default.dim("\u2502")}
241320
- ${source_default.dim("\u2502")} "input.size": 42, ${source_default.dim("\u2502")}
241321
- ${source_default.dim("\u2502")} "output.success": true ${source_default.dim("\u2502")}
241322
- ${source_default.dim("\u2502")} }, ${source_default.dim("\u2502")}
241323
- ${source_default.dim("\u2502")} ${source_default.yellow('"status"')}: { ${source_default.dim("\u2502")}
241324
- ${source_default.dim("\u2502")} "code": "OK" ${source_default.dim("// OK, ERROR, UNSET")} ${source_default.dim("\u2502")}
241325
- ${source_default.dim("\u2502")} }, ${source_default.dim("\u2502")}
241326
- ${source_default.dim("\u2502")} ${source_default.yellow('"events"')}: [] ${source_default.dim("// Optional span events")} ${source_default.dim("\u2502")}
241343
+ ${source_default.dim("\u2502")} ${source_default.yellow('"resource"')}: { ${source_default.dim("\u2502")}
241344
+ ${source_default.dim("\u2502")} ${source_default.cyan('"attributes"')}: [ ${source_default.dim("\u2502")}
241345
+ ${source_default.dim("\u2502")} { "key": "service.name", "value": { "stringValue": "..." }} ${source_default.dim("\u2502")}
241346
+ ${source_default.dim("\u2502")} ] ${source_default.dim("\u2502")}
241347
+ ${source_default.dim("\u2502")} }, ${source_default.dim("\u2502")}
241348
+ ${source_default.dim("\u2502")} ${source_default.yellow('"scopeSpans"')}: [ ${source_default.dim("\u2502")}
241349
+ ${source_default.dim("\u2502")} { ${source_default.dim("\u2502")}
241350
+ ${source_default.dim("\u2502")} ${source_default.cyan('"scope"')}: { "name": "...", "version": "..." }, ${source_default.dim("\u2502")}
241351
+ ${source_default.dim("\u2502")} ${source_default.cyan('"spans"')}: [ ${source_default.dim("\u2502")}
241352
+ ${source_default.dim("\u2502")} { ${source_default.dim("\u2502")}
241353
+ ${source_default.dim("\u2502")} "traceId": "4bf92f3577b34da6...", ${source_default.dim("// 32 hex chars")} ${source_default.dim("\u2502")}
241354
+ ${source_default.dim("\u2502")} "spanId": "00f067aa0ba902b7", ${source_default.dim("// 16 hex chars")} ${source_default.dim("\u2502")}
241355
+ ${source_default.dim("\u2502")} "name": "test:feature-name", ${source_default.dim("// Span name")} ${source_default.dim("\u2502")}
241356
+ ${source_default.dim("\u2502")} "kind": 0, ${source_default.dim("// 0=INTERNAL")} ${source_default.dim("\u2502")}
241357
+ ${source_default.dim("\u2502")} "startTimeUnixNano": "1703...", ${source_default.dim("// Unix nanoseconds")} ${source_default.dim("\u2502")}
241358
+ ${source_default.dim("\u2502")} "endTimeUnixNano": "1703...", ${source_default.dim("// Unix nanoseconds")} ${source_default.dim("\u2502")}
241359
+ ${source_default.dim("\u2502")} "attributes": [ ${source_default.dim("// Key-value pairs")} ${source_default.dim("\u2502")}
241360
+ ${source_default.dim("\u2502")} { "key": "input.size", "value": { "intValue": 42 } } ${source_default.dim("\u2502")}
241361
+ ${source_default.dim("\u2502")} ], ${source_default.dim("\u2502")}
241362
+ ${source_default.dim("\u2502")} "events": [ ${source_default.dim("// Span events")} ${source_default.dim("\u2502")}
241363
+ ${source_default.dim("\u2502")} { ${source_default.dim("\u2502")}
241364
+ ${source_default.dim("\u2502")} "timeUnixNano": "1703...", ${source_default.dim("\u2502")}
241365
+ ${source_default.dim("\u2502")} "name": "validation.started", ${source_default.dim("\u2502")}
241366
+ ${source_default.dim("\u2502")} "attributes": [...] ${source_default.dim("\u2502")}
241367
+ ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
241368
+ ${source_default.dim("\u2502")} ] ${source_default.dim("\u2502")}
241369
+ ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
241370
+ ${source_default.dim("\u2502")} ] ${source_default.dim("\u2502")}
241371
+ ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
241372
+ ${source_default.dim("\u2502")} ] ${source_default.dim("\u2502")}
241327
241373
  ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
241328
241374
  ${source_default.dim("\u2502")} ] ${source_default.dim("\u2502")}
241329
241375
  ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
241330
241376
  ${source_default.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
241331
241377
 
241332
- ${source_default.bold("Field Descriptions:")}
241378
+ ${source_default.bold("OTLP Format Details:")}
241333
241379
 
241334
- ${source_default.cyan("exportedAt")} ${source_default.dim("(string, required)")}
241335
- ISO 8601 timestamp when the file was exported
241380
+ ${source_default.cyan("resourceSpans")} ${source_default.dim("(array, required)")}
241381
+ Root array containing resource-grouped spans
241336
241382
 
241337
- ${source_default.cyan("serviceName")} ${source_default.dim("(string, required)")}
241338
- Name of the service/test suite that generated these spans
241383
+ ${source_default.cyan("resource.attributes")} ${source_default.dim("(array)")}
241384
+ Metadata about the service (e.g., service.name)
241339
241385
 
241340
- ${source_default.cyan("spanCount")} ${source_default.dim("(number, required)")}
241341
- Total number of spans in this execution
241386
+ ${source_default.cyan("scopeSpans")} ${source_default.dim("(array, required)")}
241387
+ Instrumentation scope-grouped spans
241342
241388
 
241343
241389
  ${source_default.cyan("spans")} ${source_default.dim("(array, required)")}
241344
- Array of span objects containing OTEL trace data
241345
-
241346
- ${source_default.bold("Span Object Fields:")}
241347
-
241348
- ${source_default.cyan("traceId")} ${source_default.dim("(string, 32 hex chars)")}
241349
- Unique identifier for the entire trace
241350
-
241351
- ${source_default.cyan("spanId")} ${source_default.dim("(string, 16 hex chars)")}
241352
- Unique identifier for this specific span
241390
+ Array of span objects with OTLP structure
241353
241391
 
241354
- ${source_default.cyan("parentSpanId")} ${source_default.dim("(string | null)")}
241355
- Parent span ID, or null for root span
241392
+ ${source_default.bold("Span Fields (OTLP Standard):")}
241356
241393
 
241357
- ${source_default.cyan("name")} ${source_default.dim("(string)")}
241358
- Descriptive name for the operation
241394
+ ${source_default.cyan("traceId")} ${source_default.dim("(string)")} - Unique trace identifier (32 hex chars)
241395
+ ${source_default.cyan("spanId")} ${source_default.dim("(string)")} - Unique span identifier (16 hex chars)
241396
+ ${source_default.cyan("parentSpanId")} ${source_default.dim("(string, optional)")} - Parent span ID
241397
+ ${source_default.cyan("name")} ${source_default.dim("(string)")} - Operation name
241398
+ ${source_default.cyan("kind")} ${source_default.dim("(number)")} - 0=UNSPECIFIED, 1=INTERNAL, 2=SERVER, 3=CLIENT, 4=PRODUCER, 5=CONSUMER
241399
+ ${source_default.cyan("startTimeUnixNano")} ${source_default.dim("(string)")} - Start time in Unix nanoseconds
241400
+ ${source_default.cyan("endTimeUnixNano")} ${source_default.dim("(string)")} - End time in Unix nanoseconds
241401
+ ${source_default.cyan("attributes")} ${source_default.dim("(array)")} - Key-value pairs: [{ key, value: { stringValue|intValue|... } }]
241402
+ ${source_default.cyan("events")} ${source_default.dim("(array)")} - Span events with timestamps and attributes
241403
+ ${source_default.cyan("status")} ${source_default.dim("(object)")} - Status: { code: 1=OK, 2=ERROR }
241359
241404
 
241360
- ${source_default.cyan("kind")} ${source_default.dim("(string)")}
241361
- Span kind: INTERNAL, CLIENT, SERVER, PRODUCER, CONSUMER
241362
-
241363
- ${source_default.cyan("startTime")} ${source_default.dim("(number)")}
241364
- Unix timestamp in milliseconds when span started
241365
-
241366
- ${source_default.cyan("endTime")} ${source_default.dim("(number)")}
241367
- Unix timestamp in milliseconds when span ended
241368
-
241369
- ${source_default.cyan("duration")} ${source_default.dim("(number)")}
241370
- Duration in milliseconds (endTime - startTime)
241371
-
241372
- ${source_default.cyan("attributes")} ${source_default.dim("(object)")}
241373
- Key-value pairs of event attributes (validated against canvas schema)
241374
-
241375
- ${source_default.cyan("status")} ${source_default.dim("(object)")}
241376
- Status code (OK, ERROR, UNSET) and optional message
241377
-
241378
- ${source_default.cyan("events")} ${source_default.dim("(array)")}
241379
- Optional array of timestamped events within the span
241405
+ ${source_default.bold("Attribute Value Format:")}
241406
+ { "stringValue": "..." } ${source_default.dim("// For strings")}
241407
+ { "intValue": 42 } ${source_default.dim("// For integers")}
241408
+ { "doubleValue": 3.14 } ${source_default.dim("// For floats")}
241409
+ { "boolValue": true } ${source_default.dim("// For booleans")}
241380
241410
 
241381
241411
  ${source_default.bold("Generation:")}
241382
- Set up test infrastructure with OTEL exporters that write to __executions__/
241383
- See: docs/guides/adding-opentelemetry-to-tests.md
241412
+ Use OpenTelemetry SDK with OTLP JSON exporter
241413
+ Example: @opentelemetry/sdk-trace-node + InMemorySpanExporter
241414
+ See: tests/otel-setup.ts in the add-skill repository
241384
241415
 
241385
241416
  ${source_default.bold("Validation:")}
241386
241417
  ${source_default.cyan("npx @principal-ai/principal-view-cli validate-execution <file>")}
241418
+
241419
+ ${source_default.bold("Resources:")}
241420
+ OTLP Spec: https://opentelemetry.io/docs/specs/otlp/
241421
+ OpenTelemetry JS: https://github.com/open-telemetry/opentelemetry-js
241387
241422
  `,
241388
241423
  examples: `
241389
241424
  ${source_default.bold.cyan("Complete File Examples")}