@principal-ai/principal-view-cli 0.2.2 → 0.2.4
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/dist/commands/formats.d.ts +6 -0
- package/dist/commands/formats.d.ts.map +1 -0
- package/dist/commands/formats.js +475 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +25 -5
- package/dist/index.cjs +495 -4
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/commands/formats.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAycpC,wBAAgB,oBAAoB,IAAI,OAAO,CA6B9C"}
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats command - Display documentation about file formats
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
const FORMAT_SECTIONS = {
|
|
7
|
+
overview: `
|
|
8
|
+
${chalk.bold.cyan('Principal View OTEL File Formats')}
|
|
9
|
+
${chalk.dim('═'.repeat(70))}
|
|
10
|
+
|
|
11
|
+
The Principal View OTEL workflow uses three main file types:
|
|
12
|
+
|
|
13
|
+
${chalk.bold('1. Canvas Files')} ${chalk.yellow('.otel.canvas')}
|
|
14
|
+
Define OTEL event schemas and telemetry structure for a feature.
|
|
15
|
+
These are the single source of truth for what events should be emitted.
|
|
16
|
+
|
|
17
|
+
${chalk.bold('2. Narrative Files')} ${chalk.yellow('.narrative.json')}
|
|
18
|
+
Define scenarios and templates for rendering executions as human-readable
|
|
19
|
+
narratives based on the emitted events.
|
|
20
|
+
|
|
21
|
+
${chalk.bold('3. Execution Files')} ${chalk.yellow('.otel.json')}
|
|
22
|
+
Captured OTEL spans from test runs or production code, exported for
|
|
23
|
+
visualization and validation against canvas schemas.
|
|
24
|
+
|
|
25
|
+
Run ${chalk.cyan('npx @principal-ai/principal-view-cli formats <section>')} for details on:
|
|
26
|
+
${chalk.yellow('canvas')} .otel.canvas format and event schemas
|
|
27
|
+
${chalk.yellow('narrative')} .narrative.json format and scenario structure
|
|
28
|
+
${chalk.yellow('execution')} .otel.json format for captured spans
|
|
29
|
+
${chalk.yellow('examples')} Complete example files
|
|
30
|
+
`,
|
|
31
|
+
canvas: `
|
|
32
|
+
${chalk.bold.cyan('Canvas Format (.otel.canvas)')}
|
|
33
|
+
${chalk.dim('═'.repeat(70))}
|
|
34
|
+
|
|
35
|
+
Canvas files define the OTEL event schemas for a feature. They document what
|
|
36
|
+
events should be emitted and what attributes each event must/may contain.
|
|
37
|
+
|
|
38
|
+
${chalk.bold('File Location:')}
|
|
39
|
+
${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.otel.canvas')}
|
|
40
|
+
|
|
41
|
+
${chalk.bold('Required Structure:')}
|
|
42
|
+
${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
|
|
43
|
+
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
44
|
+
${chalk.dim('│')} ${chalk.green('"nodes"')}: [ ${chalk.dim('// Array of event schemas')} ${chalk.dim('│')}
|
|
45
|
+
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
46
|
+
${chalk.dim('│')} ${chalk.yellow('"id"')}: "event-id", ${chalk.dim('// Unique identifier')} ${chalk.dim('│')}
|
|
47
|
+
${chalk.dim('│')} ${chalk.yellow('"type"')}: "text", ${chalk.dim('│')}
|
|
48
|
+
${chalk.dim('│')} ${chalk.yellow('"text"')}: "# Event Name", ${chalk.dim('// Markdown description')} ${chalk.dim('│')}
|
|
49
|
+
${chalk.dim('│')} ${chalk.yellow('"x"')}: 0, ${chalk.yellow('"y"')}: 0, ${chalk.yellow('"width"')}: 200, ${chalk.yellow('"height"')}: 100, ${chalk.dim('│')}
|
|
50
|
+
${chalk.dim('│')} ${chalk.green('"pv"')}: { ${chalk.dim('│')}
|
|
51
|
+
${chalk.dim('│')} ${chalk.cyan('"otelEvent"')}: { ${chalk.dim('// OTEL event definition')} ${chalk.dim('│')}
|
|
52
|
+
${chalk.dim('│')} ${chalk.yellow('"name"')}: "feature.event.name", ${chalk.dim('│')}
|
|
53
|
+
${chalk.dim('│')} ${chalk.cyan('"attributes"')}: { ${chalk.dim('// Required & optional attrs')} ${chalk.dim('│')}
|
|
54
|
+
${chalk.dim('│')} ${chalk.green('"required"')}: ["attr.name", ...], ${chalk.dim('│')}
|
|
55
|
+
${chalk.dim('│')} ${chalk.green('"optional"')}: ["attr.name", ...] ${chalk.dim('│')}
|
|
56
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
57
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
58
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
59
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
60
|
+
${chalk.dim('│')} ], ${chalk.dim('│')}
|
|
61
|
+
${chalk.dim('│')} ${chalk.green('"edges"')}: [], ${chalk.dim('// Optional: event relationships')} ${chalk.dim('│')}
|
|
62
|
+
${chalk.dim('│')} ${chalk.green('"pv"')}: { ${chalk.dim('│')}
|
|
63
|
+
${chalk.dim('│')} ${chalk.yellow('"name"')}: "Feature Name", ${chalk.dim('// Feature name (NOT "...Canvas")')} ${chalk.dim('│')}
|
|
64
|
+
${chalk.dim('│')} ${chalk.yellow('"version"')}: "1.0.0" ${chalk.dim('│')}
|
|
65
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
66
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
67
|
+
${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
|
|
68
|
+
|
|
69
|
+
${chalk.bold('Event Schema Best Practices:')}
|
|
70
|
+
|
|
71
|
+
${chalk.cyan('1. Canvas represents complete, encompassing functionality:')}
|
|
72
|
+
✅ API route - Full request/response cycle
|
|
73
|
+
✅ CLI command - Complete tool execution
|
|
74
|
+
✅ Business operation - End-to-end workflow
|
|
75
|
+
❌ Individual helper functions (too granular)
|
|
76
|
+
|
|
77
|
+
${chalk.cyan('2. Event naming convention:')}
|
|
78
|
+
${chalk.yellow('<feature>.<operation>.<state>')}
|
|
79
|
+
Examples:
|
|
80
|
+
- validation.started
|
|
81
|
+
- validation.complete
|
|
82
|
+
- validation.error
|
|
83
|
+
- import.parsing.complete
|
|
84
|
+
|
|
85
|
+
${chalk.cyan('3. Start simple (2-4 events):')}
|
|
86
|
+
- ${chalk.green('started')} - When feature begins
|
|
87
|
+
- ${chalk.green('complete')} - When feature succeeds
|
|
88
|
+
- ${chalk.green('error')} - When feature fails
|
|
89
|
+
- (optional) progress events for clear intermediate steps
|
|
90
|
+
|
|
91
|
+
${chalk.cyan('4. Attribute naming conventions:')}
|
|
92
|
+
${chalk.yellow('<category>.<name>')}
|
|
93
|
+
- input.* (input.size, input.recordCount)
|
|
94
|
+
- output.* (output.count, output.success)
|
|
95
|
+
- result.* (result.validCount, result.invalidCount)
|
|
96
|
+
- error.* (error.type, error.message, error.stage)
|
|
97
|
+
- duration.* (duration.ms)
|
|
98
|
+
|
|
99
|
+
${chalk.cyan('5. Required vs Optional attributes:')}
|
|
100
|
+
- ${chalk.green('required')}: Essential data needed for validation
|
|
101
|
+
- ${chalk.yellow('optional')}: Nice-to-have context, won't fail validation if missing
|
|
102
|
+
|
|
103
|
+
${chalk.bold('Validation:')}
|
|
104
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli validate')}
|
|
105
|
+
`,
|
|
106
|
+
narrative: `
|
|
107
|
+
${chalk.bold.cyan('Narrative Format (.narrative.json)')}
|
|
108
|
+
${chalk.dim('═'.repeat(70))}
|
|
109
|
+
|
|
110
|
+
Narrative files define scenarios for rendering execution data as human-readable
|
|
111
|
+
stories. They evaluate conditions against captured events to select the best
|
|
112
|
+
matching narrative template.
|
|
113
|
+
|
|
114
|
+
${chalk.bold('File Location:')}
|
|
115
|
+
${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.narrative.json')}
|
|
116
|
+
${chalk.dim('(co-located with corresponding .otel.canvas file)')}
|
|
117
|
+
|
|
118
|
+
${chalk.bold('Required Structure:')}
|
|
119
|
+
${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
|
|
120
|
+
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
121
|
+
${chalk.dim('│')} ${chalk.green('"name"')}: "Feature Name", ${chalk.dim('// NOT "Feature Name Narratives"')} ${chalk.dim('│')}
|
|
122
|
+
${chalk.dim('│')} ${chalk.green('"description"')}: "What the feature does", ${chalk.dim('// Purpose, not "Narratives for..."')} ${chalk.dim('│')}
|
|
123
|
+
${chalk.dim('│')} ${chalk.green('"scenarios"')}: [ ${chalk.dim('│')}
|
|
124
|
+
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
125
|
+
${chalk.dim('│')} ${chalk.yellow('"priority"')}: 1, ${chalk.dim('// Lower = higher priority')} ${chalk.dim('│')}
|
|
126
|
+
${chalk.dim('│')} ${chalk.yellow('"condition"')}: "...", ${chalk.dim('// JSONPath/logic expression')} ${chalk.dim('│')}
|
|
127
|
+
${chalk.dim('│')} ${chalk.yellow('"template"')}: { ${chalk.dim('// Narrative template')} ${chalk.dim('│')}
|
|
128
|
+
${chalk.dim('│')} ${chalk.cyan('"summary"')}: "...", ${chalk.dim('// One-line summary')} ${chalk.dim('│')}
|
|
129
|
+
${chalk.dim('│')} ${chalk.cyan('"details"')}: [ ${chalk.dim('// Step-by-step details')} ${chalk.dim('│')}
|
|
130
|
+
${chalk.dim('│')} "Step 1: ...", ${chalk.dim('│')}
|
|
131
|
+
${chalk.dim('│')} "Step 2: ...", ${chalk.dim('│')}
|
|
132
|
+
${chalk.dim('│')} ] ${chalk.dim('│')}
|
|
133
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
134
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
135
|
+
${chalk.dim('│')} ] ${chalk.dim('│')}
|
|
136
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
137
|
+
${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
|
|
138
|
+
|
|
139
|
+
${chalk.bold('Naming Guidelines:')}
|
|
140
|
+
|
|
141
|
+
❌ DON'T append "Narratives" to the name:
|
|
142
|
+
"name": "Package Processor Narratives"
|
|
143
|
+
|
|
144
|
+
✅ DO use the feature name directly:
|
|
145
|
+
"name": "Package Processor"
|
|
146
|
+
|
|
147
|
+
❌ DON'T prefix description with boilerplate:
|
|
148
|
+
"description": "Human-readable narratives for package extraction..."
|
|
149
|
+
|
|
150
|
+
✅ DO describe the feature's purpose:
|
|
151
|
+
"description": "Package extraction and analysis from repository file trees"
|
|
152
|
+
|
|
153
|
+
${chalk.bold('Scenario Best Practices:')}
|
|
154
|
+
|
|
155
|
+
${chalk.cyan('1. Priority ordering (scenarios evaluated in order):')}
|
|
156
|
+
- ${chalk.green('1-10')} Specific scenarios (success/failure cases)
|
|
157
|
+
- ${chalk.yellow('999')} Fallback scenario (catches anything)
|
|
158
|
+
|
|
159
|
+
${chalk.cyan('2. Standard scenario set:')}
|
|
160
|
+
- ${chalk.green('Success')} (priority 1): Feature completed successfully
|
|
161
|
+
- ${chalk.yellow('Failure')} (priority 2): Feature encountered error
|
|
162
|
+
- ${chalk.dim('Fallback')} (priority 999): Generic execution captured
|
|
163
|
+
|
|
164
|
+
${chalk.cyan('3. Template interpolation:')}
|
|
165
|
+
Use {{path.to.value}} to reference event attributes
|
|
166
|
+
Examples:
|
|
167
|
+
- {{record.count}}
|
|
168
|
+
- {{error.message}}
|
|
169
|
+
- {{result.invalidCount}}
|
|
170
|
+
|
|
171
|
+
${chalk.cyan('4. Template style:')}
|
|
172
|
+
- Clear, concise summary line
|
|
173
|
+
- 3-5 detail steps showing workflow
|
|
174
|
+
- Use emojis for visual scanning (✅ ❌ 📋)
|
|
175
|
+
- Include key metrics and IDs
|
|
176
|
+
|
|
177
|
+
${chalk.bold('Validation:')}
|
|
178
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli narrative validate')}
|
|
179
|
+
`,
|
|
180
|
+
execution: `
|
|
181
|
+
${chalk.bold.cyan('Execution Format (.otel.json)')}
|
|
182
|
+
${chalk.dim('═'.repeat(70))}
|
|
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
|
|
186
|
+
and validation against canvas schemas.
|
|
187
|
+
|
|
188
|
+
${chalk.bold('File Location:')}
|
|
189
|
+
${chalk.yellow('__executions__/')}${chalk.dim('<feature-name>.otel.json')}
|
|
190
|
+
${chalk.dim('(auto-generated by test infrastructure)')}
|
|
191
|
+
|
|
192
|
+
${chalk.bold('IMPORTANT:')} __executions__/ directory must be committed to git!
|
|
193
|
+
|
|
194
|
+
${chalk.bold('File Structure:')}
|
|
195
|
+
${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
|
|
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('│')}
|
|
201
|
+
${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('│')}
|
|
218
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
219
|
+
${chalk.dim('│')} ] ${chalk.dim('│')}
|
|
220
|
+
${chalk.dim('│')} } ${chalk.dim('│')}
|
|
221
|
+
${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
|
|
222
|
+
|
|
223
|
+
${chalk.bold('Field Descriptions:')}
|
|
224
|
+
|
|
225
|
+
${chalk.cyan('exportedAt')} ${chalk.dim('(string, required)')}
|
|
226
|
+
ISO 8601 timestamp when the file was exported
|
|
227
|
+
|
|
228
|
+
${chalk.cyan('serviceName')} ${chalk.dim('(string, required)')}
|
|
229
|
+
Name of the service/test suite that generated these spans
|
|
230
|
+
|
|
231
|
+
${chalk.cyan('spanCount')} ${chalk.dim('(number, required)')}
|
|
232
|
+
Total number of spans in this execution
|
|
233
|
+
|
|
234
|
+
${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
|
|
271
|
+
|
|
272
|
+
${chalk.bold('Generation:')}
|
|
273
|
+
Set up test infrastructure with OTEL exporters that write to __executions__/
|
|
274
|
+
See: docs/guides/adding-opentelemetry-to-tests.md
|
|
275
|
+
|
|
276
|
+
${chalk.bold('Validation:')}
|
|
277
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution <file>')}
|
|
278
|
+
`,
|
|
279
|
+
examples: `
|
|
280
|
+
${chalk.bold.cyan('Complete File Examples')}
|
|
281
|
+
${chalk.dim('═'.repeat(70))}
|
|
282
|
+
|
|
283
|
+
${chalk.bold('Example 1: Data Validator Canvas')}
|
|
284
|
+
${chalk.dim('─'.repeat(70))}
|
|
285
|
+
${chalk.yellow('.principal-views/data-validator.otel.canvas')}
|
|
286
|
+
|
|
287
|
+
{
|
|
288
|
+
"nodes": [
|
|
289
|
+
{
|
|
290
|
+
"id": "validation-started",
|
|
291
|
+
"type": "text",
|
|
292
|
+
"text": "# validation.started\\n\\nEmitted when validation begins",
|
|
293
|
+
"x": 0, "y": 0, "width": 200, "height": 100,
|
|
294
|
+
"pv": {
|
|
295
|
+
"otelEvent": {
|
|
296
|
+
"name": "validation.started",
|
|
297
|
+
"attributes": {
|
|
298
|
+
"required": ["input.recordCount"],
|
|
299
|
+
"optional": ["input.source"]
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"id": "validation-complete",
|
|
306
|
+
"type": "text",
|
|
307
|
+
"text": "# validation.complete\\n\\nEmitted when validation succeeds",
|
|
308
|
+
"x": 250, "y": 0, "width": 200, "height": 100,
|
|
309
|
+
"pv": {
|
|
310
|
+
"otelEvent": {
|
|
311
|
+
"name": "validation.complete",
|
|
312
|
+
"attributes": {
|
|
313
|
+
"required": ["result.validCount", "result.invalidCount"],
|
|
314
|
+
"optional": ["duration.ms"]
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"id": "validation-error",
|
|
321
|
+
"type": "text",
|
|
322
|
+
"text": "# validation.error\\n\\nEmitted when validation fails",
|
|
323
|
+
"x": 500, "y": 0, "width": 200, "height": 100,
|
|
324
|
+
"pv": {
|
|
325
|
+
"otelEvent": {
|
|
326
|
+
"name": "validation.error",
|
|
327
|
+
"attributes": {
|
|
328
|
+
"required": ["error.type", "error.message"],
|
|
329
|
+
"optional": ["error.stage"]
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
],
|
|
335
|
+
"edges": [],
|
|
336
|
+
"pv": {
|
|
337
|
+
"name": "Data Validator",
|
|
338
|
+
"version": "1.0.0"
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
${chalk.bold('Example 2: Narrative Scenarios')}
|
|
343
|
+
${chalk.dim('─'.repeat(70))}
|
|
344
|
+
${chalk.yellow('.principal-views/data-validator.narrative.json')}
|
|
345
|
+
|
|
346
|
+
{
|
|
347
|
+
"name": "Data Validator",
|
|
348
|
+
"description": "Validates data records against defined schemas",
|
|
349
|
+
"scenarios": [
|
|
350
|
+
{
|
|
351
|
+
"priority": 1,
|
|
352
|
+
"condition": "events[?name=='validation.complete']",
|
|
353
|
+
"template": {
|
|
354
|
+
"summary": "✅ Validated {{result.validCount}} records successfully",
|
|
355
|
+
"details": [
|
|
356
|
+
"🔍 Started validation",
|
|
357
|
+
"📊 Processed {{input.recordCount}} records",
|
|
358
|
+
"✅ {{result.validCount}} valid",
|
|
359
|
+
"❌ {{result.invalidCount}} invalid"
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"priority": 2,
|
|
365
|
+
"condition": "events[?name=='validation.error']",
|
|
366
|
+
"template": {
|
|
367
|
+
"summary": "❌ Validation failed: {{error.message}}",
|
|
368
|
+
"details": [
|
|
369
|
+
"🔍 Started validation",
|
|
370
|
+
"💥 Error occurred: {{error.message}}",
|
|
371
|
+
"🏷️ Error type: {{error.type}}"
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"priority": 999,
|
|
377
|
+
"condition": "true",
|
|
378
|
+
"template": {
|
|
379
|
+
"summary": "📋 Validation execution captured",
|
|
380
|
+
"details": [
|
|
381
|
+
"📦 Captured {{spans.length}} events",
|
|
382
|
+
"⏱️ Duration: {{duration.ms}}ms"
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
${chalk.bold('Example 3: Execution File')}
|
|
390
|
+
${chalk.dim('─'.repeat(70))}
|
|
391
|
+
${chalk.yellow('__executions__/data-validator.otel.json')}
|
|
392
|
+
|
|
393
|
+
{
|
|
394
|
+
"exportedAt": "2025-01-21T10:30:45.123Z",
|
|
395
|
+
"serviceName": "my-app-tests",
|
|
396
|
+
"spanCount": 3,
|
|
397
|
+
"spans": [
|
|
398
|
+
{
|
|
399
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
400
|
+
"spanId": "00f067aa0ba902b7",
|
|
401
|
+
"parentSpanId": null,
|
|
402
|
+
"name": "test:data-validator",
|
|
403
|
+
"kind": "INTERNAL",
|
|
404
|
+
"startTime": 1703548800000,
|
|
405
|
+
"endTime": 1703548800150,
|
|
406
|
+
"duration": 150,
|
|
407
|
+
"attributes": {
|
|
408
|
+
"test.name": "data validator success case"
|
|
409
|
+
},
|
|
410
|
+
"status": { "code": "OK" },
|
|
411
|
+
"events": []
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
415
|
+
"spanId": "abc123def4567890",
|
|
416
|
+
"parentSpanId": "00f067aa0ba902b7",
|
|
417
|
+
"name": "validation.started",
|
|
418
|
+
"kind": "INTERNAL",
|
|
419
|
+
"startTime": 1703548800010,
|
|
420
|
+
"endTime": 1703548800015,
|
|
421
|
+
"duration": 5,
|
|
422
|
+
"attributes": {
|
|
423
|
+
"input.recordCount": 100,
|
|
424
|
+
"input.source": "test-data.csv"
|
|
425
|
+
},
|
|
426
|
+
"status": { "code": "OK" },
|
|
427
|
+
"events": []
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
|
431
|
+
"spanId": "def789abc1234567",
|
|
432
|
+
"parentSpanId": "00f067aa0ba902b7",
|
|
433
|
+
"name": "validation.complete",
|
|
434
|
+
"kind": "INTERNAL",
|
|
435
|
+
"startTime": 1703548800140,
|
|
436
|
+
"endTime": 1703548800145,
|
|
437
|
+
"duration": 5,
|
|
438
|
+
"attributes": {
|
|
439
|
+
"result.validCount": 95,
|
|
440
|
+
"result.invalidCount": 5,
|
|
441
|
+
"duration.ms": 130
|
|
442
|
+
},
|
|
443
|
+
"status": { "code": "OK" },
|
|
444
|
+
"events": []
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
${chalk.bold('Next Steps:')}
|
|
450
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli validate')} Validate canvas
|
|
451
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli narrative validate')} Validate narratives
|
|
452
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution')} Validate execution
|
|
453
|
+
`,
|
|
454
|
+
};
|
|
455
|
+
export function createFormatsCommand() {
|
|
456
|
+
const command = new Command('formats');
|
|
457
|
+
command
|
|
458
|
+
.description('Display documentation about file formats')
|
|
459
|
+
.argument('[section]', 'Section to display: overview, canvas, narrative, execution, examples')
|
|
460
|
+
.action((section) => {
|
|
461
|
+
const validSections = Object.keys(FORMAT_SECTIONS);
|
|
462
|
+
if (!section) {
|
|
463
|
+
console.log(FORMAT_SECTIONS.overview);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const normalizedSection = section.toLowerCase();
|
|
467
|
+
if (!validSections.includes(normalizedSection)) {
|
|
468
|
+
console.log(chalk.red(`Unknown section: ${section}`));
|
|
469
|
+
console.log(`Valid sections: ${validSections.join(', ')}`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
console.log(FORMAT_SECTIONS[normalizedSection]);
|
|
473
|
+
});
|
|
474
|
+
return command;
|
|
475
|
+
}
|
|
@@ -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;
|
|
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;AAqrCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAiI/C"}
|
|
@@ -225,7 +225,7 @@ const ALLOWED_CANVAS_FIELDS = {
|
|
|
225
225
|
'name',
|
|
226
226
|
'description',
|
|
227
227
|
'otel',
|
|
228
|
-
'
|
|
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
|
|
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
|
|
377
|
-
if (nodePv.
|
|
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.
|
|
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,6 +657,15 @@ 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
670
|
// Validate that all source files exist (if repository path is provided)
|
|
651
671
|
if (Array.isArray(nodePv.sources) && repositoryPath) {
|