@principal-ai/principal-view-cli 0.3.8 → 0.4.1

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/index.cjs CHANGED
@@ -8971,9 +8971,9 @@ var require_out4 = __commonJS({
8971
8971
  }
8972
8972
  });
8973
8973
 
8974
- // node_modules/globby/node_modules/ignore/index.js
8974
+ // node_modules/ignore/index.js
8975
8975
  var require_ignore = __commonJS({
8976
- "node_modules/globby/node_modules/ignore/index.js"(exports2, module2) {
8976
+ "node_modules/ignore/index.js"(exports2, module2) {
8977
8977
  function makeArray(subject) {
8978
8978
  return Array.isArray(subject) ? subject : [subject];
8979
8979
  }
@@ -241190,19 +241190,6 @@ function getNestedValue(obj, path4) {
241190
241190
  }
241191
241191
  return current;
241192
241192
  }
241193
- function setNestedValue(obj, path4, value) {
241194
- const keys = path4.split(".");
241195
- let current = obj;
241196
- for (let i = 0; i < keys.length - 1; i++) {
241197
- const key = keys[i];
241198
- if (current[key] === void 0 || typeof current[key] !== "object") {
241199
- current[key] = {};
241200
- }
241201
- current = current[key];
241202
- }
241203
- const lastKey = keys[keys.length - 1];
241204
- current[lastKey] = value;
241205
- }
241206
241193
  function computeAggregates(events) {
241207
241194
  const aggregates = {
241208
241195
  // Event counts
@@ -241216,15 +241203,12 @@ function computeAggregates(events) {
241216
241203
  "warnLogs.count": events.filter((e) => e.type === "log" && (e.severityNumber ?? 0) >= 13 && (e.severityNumber ?? 0) <= 16).length,
241217
241204
  "debugLogs.count": events.filter((e) => e.type === "log" && (e.severityNumber ?? 0) >= 5 && (e.severityNumber ?? 0) <= 8).length
241218
241205
  };
241219
- for (const event of events) {
241206
+ const summaryEventPatterns = [".complete", ".summary", ".error", ".started"];
241207
+ const summaryEvents = events.filter((e) => summaryEventPatterns.some((pattern) => e.name.endsWith(pattern)));
241208
+ for (const event of summaryEvents) {
241220
241209
  if (event.attributes) {
241221
241210
  for (const [key, value] of Object.entries(event.attributes)) {
241222
- if (getNestedValue(aggregates, key) === void 0) {
241223
- setNestedValue(aggregates, key, value);
241224
- if (key.includes(".")) {
241225
- aggregates[key] = value;
241226
- }
241227
- }
241211
+ aggregates[key] = value;
241228
241212
  }
241229
241213
  }
241230
241214
  }
@@ -241233,6 +241217,21 @@ function computeAggregates(events) {
241233
241217
 
241234
241218
  // node_modules/@principal-ai/principal-view-core/dist/narrative/template-parser.js
241235
241219
  var import_handlebars = __toESM(require_lib());
241220
+ import_handlebars.default.registerHelper("eq", (a, b) => a === b);
241221
+ import_handlebars.default.registerHelper("ne", (a, b) => a !== b);
241222
+ import_handlebars.default.registerHelper("lt", (a, b) => a < b);
241223
+ import_handlebars.default.registerHelper("gt", (a, b) => a > b);
241224
+ import_handlebars.default.registerHelper("lte", (a, b) => a <= b);
241225
+ import_handlebars.default.registerHelper("gte", (a, b) => a >= b);
241226
+ import_handlebars.default.registerHelper("and", (...args) => {
241227
+ const values = args.slice(0, -1);
241228
+ return values.every(Boolean);
241229
+ });
241230
+ import_handlebars.default.registerHelper("or", (...args) => {
241231
+ const values = args.slice(0, -1);
241232
+ return values.some(Boolean);
241233
+ });
241234
+ import_handlebars.default.registerHelper("not", (a) => !a);
241236
241235
  function parseTemplate(template, context) {
241237
241236
  try {
241238
241237
  const handlebarTemplate = import_handlebars.default.compile(template, { noEscape: true });
@@ -241319,7 +241318,25 @@ function renderSpanTree(tree, scenario, context, formatting2) {
241319
241318
  const parts = [];
241320
241319
  for (const node of tree) {
241321
241320
  const indent = (formatting2.indentPerLevel || " ").repeat(node.depth);
241322
- const eventContext = { ...context, ...node.span.attributes, span: node.span };
241321
+ const eventContext = { ...context };
241322
+ if (node.span.attributes) {
241323
+ for (const [key, value] of Object.entries(node.span.attributes)) {
241324
+ if (key.includes(".")) {
241325
+ const parts2 = key.split(".");
241326
+ let current = eventContext;
241327
+ for (let i = 0; i < parts2.length - 1; i++) {
241328
+ if (!current[parts2[i]] || typeof current[parts2[i]] !== "object") {
241329
+ current[parts2[i]] = {};
241330
+ }
241331
+ current = current[parts2[i]];
241332
+ }
241333
+ current[parts2[parts2.length - 1]] = value;
241334
+ } else {
241335
+ eventContext[key] = value;
241336
+ }
241337
+ }
241338
+ }
241339
+ eventContext.span = node.span;
241323
241340
  if (scenario.template.span) {
241324
241341
  const spanText = parseTemplate(scenario.template.span, eventContext);
241325
241342
  parts.push(indent + spanText);
@@ -241353,7 +241370,24 @@ function renderTimeline(events, scenario, context, formatting2) {
241353
241370
  return aTime - bTime;
241354
241371
  });
241355
241372
  for (const event of sorted) {
241356
- const eventContext = { ...context, ...event.attributes };
241373
+ const eventContext = { ...context };
241374
+ if (event.attributes) {
241375
+ for (const [key, value] of Object.entries(event.attributes)) {
241376
+ if (key.includes(".")) {
241377
+ const parts2 = key.split(".");
241378
+ let current = eventContext;
241379
+ for (let i = 0; i < parts2.length - 1; i++) {
241380
+ if (!current[parts2[i]] || typeof current[parts2[i]] !== "object") {
241381
+ current[parts2[i]] = {};
241382
+ }
241383
+ current = current[parts2[i]];
241384
+ }
241385
+ current[parts2[parts2.length - 1]] = value;
241386
+ } else {
241387
+ eventContext[key] = value;
241388
+ }
241389
+ }
241390
+ }
241357
241391
  let eventText;
241358
241392
  if (event.type === "log") {
241359
241393
  eventText = renderLog(event, scenario, { ...eventContext, log: event }, formatting2);
@@ -241673,8 +241707,8 @@ var NarrativeValidator = class {
241673
241707
  }
241674
241708
  const canvasEvents = /* @__PURE__ */ new Set();
241675
241709
  for (const node of canvas.nodes) {
241676
- if (node.pv?.event?.name) {
241677
- canvasEvents.add(node.pv.event.name);
241710
+ if (node.pv?.event && typeof node.pv.event === "string") {
241711
+ canvasEvents.add(node.pv.event);
241678
241712
  }
241679
241713
  }
241680
241714
  const narrativeEvents = /* @__PURE__ */ new Set();
@@ -241688,7 +241722,9 @@ var NarrativeValidator = class {
241688
241722
  }
241689
241723
  if (scenario.template?.events) {
241690
241724
  for (const eventName of Object.keys(scenario.template.events)) {
241691
- narrativeEvents.add(eventName);
241725
+ if (!eventName.includes("*")) {
241726
+ narrativeEvents.add(eventName);
241727
+ }
241692
241728
  }
241693
241729
  }
241694
241730
  }
@@ -242104,7 +242140,7 @@ var NarrativeValidator = class {
242104
242140
  path: path4,
242105
242141
  message: `Incomplete conditional expression: ${expr}`,
242106
242142
  impact: "Template will fail to render",
242107
- suggestion: 'Conditional format: {condition ? "true" : "false"}',
242143
+ suggestion: "Use Handlebars syntax: {{#if condition}}true{{else}}false{{/if}}",
242108
242144
  fixable: false
242109
242145
  });
242110
242146
  }
@@ -242112,12 +242148,137 @@ var NarrativeValidator = class {
242112
242148
  return violations;
242113
242149
  }
242114
242150
  /**
242115
- * Check attribute references (warning level)
242151
+ * Check attribute references against execution data
242152
+ *
242153
+ * Validates that:
242154
+ * - Attributes referenced in templates exist in execution data
242155
+ * - Object attributes are accessed via properties (not used directly)
242156
+ * - Attribute names are correct (catches typos)
242116
242157
  */
242117
- checkAttributeReferences(_context) {
242158
+ checkAttributeReferences(context) {
242118
242159
  const violations = [];
242160
+ const { narrative, narrativePath, executionData } = context;
242161
+ if (!executionData) {
242162
+ return violations;
242163
+ }
242164
+ const { aggregates, eventAttributes } = executionData;
242165
+ for (const scenario of narrative.scenarios) {
242166
+ const scenarioPath = `scenarios[${scenario.id}]`;
242167
+ if (scenario.template.introduction) {
242168
+ const attrs = this.extractAttributeReferences(scenario.template.introduction);
242169
+ violations.push(...this.validateAttributes(
242170
+ attrs,
242171
+ aggregates,
242172
+ null,
242173
+ // introduction doesn't have specific event context
242174
+ narrativePath,
242175
+ `${scenarioPath}.template.introduction`
242176
+ ));
242177
+ }
242178
+ if (scenario.template.events) {
242179
+ for (const [eventName, eventTemplate] of Object.entries(scenario.template.events)) {
242180
+ const attrs = this.extractAttributeReferences(eventTemplate);
242181
+ const eventAttrs = eventAttributes.get(eventName);
242182
+ violations.push(...this.validateAttributes(attrs, aggregates, eventAttrs || null, narrativePath, `${scenarioPath}.template.events.${eventName}`, eventName));
242183
+ }
242184
+ }
242185
+ if (scenario.template.summary) {
242186
+ const attrs = this.extractAttributeReferences(scenario.template.summary);
242187
+ violations.push(...this.validateAttributes(
242188
+ attrs,
242189
+ aggregates,
242190
+ null,
242191
+ // summary uses global aggregates
242192
+ narrativePath,
242193
+ `${scenarioPath}.template.summary`
242194
+ ));
242195
+ }
242196
+ }
242119
242197
  return violations;
242120
242198
  }
242199
+ /**
242200
+ * Validate a list of attribute references against available data
242201
+ *
242202
+ * @param attributes - Attribute paths to validate
242203
+ * @param aggregates - Global aggregate attributes
242204
+ * @param eventAttributes - Event-specific attributes (if validating event template)
242205
+ * @param file - File path for violation reporting
242206
+ * @param path - JSON path for violation reporting
242207
+ * @param eventName - Event name (if validating event template)
242208
+ * @returns Array of violations found
242209
+ */
242210
+ validateAttributes(attributes, aggregates, eventAttributes, file, path4, eventName) {
242211
+ const violations = [];
242212
+ for (const attr of attributes) {
242213
+ const globalValue = aggregates[attr];
242214
+ const eventValue = eventAttributes?.[attr];
242215
+ if (globalValue === void 0 && eventValue === void 0) {
242216
+ const allKeys = [
242217
+ ...Object.keys(aggregates),
242218
+ ...eventAttributes ? Object.keys(eventAttributes) : []
242219
+ ];
242220
+ const similar = this.findSimilarAttributes(attr, allKeys);
242221
+ violations.push({
242222
+ ruleId: "narrative-attribute-undefined",
242223
+ severity: "warn",
242224
+ file,
242225
+ path: path4,
242226
+ message: eventName ? `Attribute "{{${attr}}}" not found in event "${eventName}" or global aggregates` : `Attribute "{{${attr}}}" not found in execution data`,
242227
+ impact: 'Template will render as empty or "undefined"',
242228
+ suggestion: similar.length > 0 ? `Did you mean: ${similar.join(", ")}?` : void 0,
242229
+ fixable: false
242230
+ });
242231
+ continue;
242232
+ }
242233
+ const value = eventValue !== void 0 ? eventValue : globalValue;
242234
+ if (this.isObjectType(value)) {
242235
+ const objectKeys = Object.keys(value);
242236
+ const suggestions = objectKeys.slice(0, 3).map((k) => `{{${attr}.${k}}}`);
242237
+ violations.push({
242238
+ ruleId: "narrative-attribute-object",
242239
+ severity: "warn",
242240
+ file,
242241
+ path: path4,
242242
+ message: `Attribute "{{${attr}}}" is an object and will render as "[object Object]"`,
242243
+ impact: 'Template will show "[object Object]" instead of useful data',
242244
+ suggestion: `Access a property instead: ${suggestions.join(", ")}`,
242245
+ fixable: false
242246
+ });
242247
+ }
242248
+ }
242249
+ return violations;
242250
+ }
242251
+ /**
242252
+ * Find similar attribute names for helpful suggestions
242253
+ *
242254
+ * Uses simple string similarity (Levenshtein-like) to find typos
242255
+ *
242256
+ * @param target - The attribute being searched for
242257
+ * @param available - Available attribute names
242258
+ * @returns Array of similar attribute names (max 3)
242259
+ */
242260
+ findSimilarAttributes(target, available) {
242261
+ const similar = [];
242262
+ for (const attr of available) {
242263
+ if (attr.startsWith(target) || target.startsWith(attr)) {
242264
+ similar.push({ attr, score: 10 });
242265
+ continue;
242266
+ }
242267
+ if (attr.includes(target) || target.includes(attr)) {
242268
+ similar.push({ attr, score: 5 });
242269
+ continue;
242270
+ }
242271
+ const targetParts = target.split(".");
242272
+ const attrParts = attr.split(".");
242273
+ if (targetParts.length === attrParts.length) {
242274
+ const matchingParts = targetParts.filter((p, i) => p === attrParts[i]).length;
242275
+ if (matchingParts > 0) {
242276
+ similar.push({ attr, score: matchingParts });
242277
+ }
242278
+ }
242279
+ }
242280
+ return similar.sort((a, b) => b.score - a.score).slice(0, 3).map((s) => s.attr);
242281
+ }
242121
242282
  /**
242122
242283
  * Check formatting options
242123
242284
  */
@@ -242219,6 +242380,71 @@ var NarrativeValidator = class {
242219
242380
  }
242220
242381
  return match[1];
242221
242382
  }
242383
+ /**
242384
+ * Extract attribute references from Handlebars template
242385
+ *
242386
+ * Parses template strings like:
242387
+ * - "{{source}}" -> ["source"]
242388
+ * - "{{source.url}}" -> ["source.url"]
242389
+ * - "{{#if options.global}}" -> ["options.global"]
242390
+ * - "{{#if (eq install.mode 'symlink')}}" -> ["install.mode"]
242391
+ *
242392
+ * @param template - Handlebars template string
242393
+ * @returns Array of attribute paths referenced in the template
242394
+ */
242395
+ extractAttributeReferences(template) {
242396
+ const attributes = /* @__PURE__ */ new Set();
242397
+ const expressionPattern = /\{\{([^}]+)\}\}/g;
242398
+ let match;
242399
+ while ((match = expressionPattern.exec(template)) !== null) {
242400
+ const expression = match[1].trim();
242401
+ if (expression.startsWith("/")) {
242402
+ continue;
242403
+ }
242404
+ if (expression.startsWith("#")) {
242405
+ const helperMatch = expression.match(/^#\w+\s+(.+)$/);
242406
+ if (helperMatch) {
242407
+ this.extractAttributesFromExpression(helperMatch[1], attributes);
242408
+ }
242409
+ continue;
242410
+ }
242411
+ this.extractAttributesFromExpression(expression, attributes);
242412
+ }
242413
+ return Array.from(attributes);
242414
+ }
242415
+ /**
242416
+ * Extract attribute references from a single Handlebars expression
242417
+ *
242418
+ * Handles:
242419
+ * - Simple references: source.url
242420
+ * - Helper calls: (eq install.mode 'symlink')
242421
+ * - Nested expressions
242422
+ *
242423
+ * @param expression - The expression to parse
242424
+ * @param attributes - Set to add found attributes to
242425
+ */
242426
+ extractAttributesFromExpression(expression, attributes) {
242427
+ const cleaned = expression.replace(/^\(|\)$/g, "").trim();
242428
+ const parts = cleaned.split(/\s+/);
242429
+ for (const part of parts) {
242430
+ if (part.match(/^(if|unless|each|with|eq|ne|lt|gt|lte|gte|and|or|not)$/) || part.match(/^['"].*['"]$/) || part.match(/^\d+$/) || part.match(/^(true|false|null|undefined)$/)) {
242431
+ continue;
242432
+ }
242433
+ const cleanPart = part.replace(/['"()]/g, "");
242434
+ if (cleanPart && cleanPart.match(/^[a-zA-Z_][a-zA-Z0-9_.]*$/)) {
242435
+ attributes.add(cleanPart);
242436
+ }
242437
+ }
242438
+ }
242439
+ /**
242440
+ * Check if an attribute is an object type
242441
+ *
242442
+ * @param value - The attribute value to check
242443
+ * @returns true if value is a plain object (not array, not null)
242444
+ */
242445
+ isObjectType(value) {
242446
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
242447
+ }
242222
242448
  };
242223
242449
  function createNarrativeValidator() {
242224
242450
  return new NarrativeValidator();
@@ -247413,13 +247639,18 @@ ${source_default.dim("\u2502")} ${source_default.yellow('"type"')}: "text"
247413
247639
  ${source_default.dim("\u2502")} ${source_default.yellow('"text"')}: "# Event Name", ${source_default.dim("// Markdown description")} ${source_default.dim("\u2502")}
247414
247640
  ${source_default.dim("\u2502")} ${source_default.yellow('"x"')}: 0, ${source_default.yellow('"y"')}: 0, ${source_default.yellow('"width"')}: 200, ${source_default.yellow('"height"')}: 100, ${source_default.dim("\u2502")}
247415
247641
  ${source_default.dim("\u2502")} ${source_default.green('"pv"')}: { ${source_default.dim("\u2502")}
247416
- ${source_default.dim("\u2502")} ${source_default.cyan('"otelEvent"')}: { ${source_default.dim("// OTEL event definition")} ${source_default.dim("\u2502")}
247417
- ${source_default.dim("\u2502")} ${source_default.yellow('"name"')}: "feature.event.name", ${source_default.dim("\u2502")}
247418
- ${source_default.dim("\u2502")} ${source_default.cyan('"attributes"')}: { ${source_default.dim("// Required & optional attrs")} ${source_default.dim("\u2502")}
247419
- ${source_default.dim("\u2502")} ${source_default.green('"required"')}: ["attr.name", ...], ${source_default.dim("\u2502")}
247420
- ${source_default.dim("\u2502")} ${source_default.green('"optional"')}: ["attr.name", ...] ${source_default.dim("\u2502")}
247421
- ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247422
- ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247642
+ ${source_default.dim("\u2502")} ${source_default.cyan('"event"')}: "feature.event.name", ${source_default.dim("// Event name")} ${source_default.dim("\u2502")}
247643
+ ${source_default.dim("\u2502")} ${source_default.cyan('"sources"')}: ["src/file.ts"], ${source_default.dim("// Source files")} ${source_default.dim("\u2502")}
247644
+ ${source_default.dim("\u2502")} ${source_default.cyan('"otel"')}: { ${source_default.dim("// OTEL metadata")} ${source_default.dim("\u2502")}
247645
+ ${source_default.dim("\u2502")} ${source_default.yellow('"kind"')}: "event", ${source_default.dim("\u2502")}
247646
+ ${source_default.dim("\u2502")} ${source_default.yellow('"category"')}: "lifecycle" ${source_default.dim("\u2502")}
247647
+ ${source_default.dim("\u2502")} }, ${source_default.dim("\u2502")}
247648
+ ${source_default.dim("\u2502")} ${source_default.cyan('"dataSchema"')}: { ${source_default.dim("// Attribute definitions")} ${source_default.dim("\u2502")}
247649
+ ${source_default.dim("\u2502")} ${source_default.yellow('"attr.name"')}: { ${source_default.dim("\u2502")}
247650
+ ${source_default.dim("\u2502")} ${source_default.green('"type"')}: "string", ${source_default.dim("\u2502")}
247651
+ ${source_default.dim("\u2502")} ${source_default.green('"required"')}: true ${source_default.dim("\u2502")}
247652
+ ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247653
+ ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247423
247654
  ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247424
247655
  ${source_default.dim("\u2502")} } ${source_default.dim("\u2502")}
247425
247656
  ${source_default.dim("\u2502")} ], ${source_default.dim("\u2502")}
@@ -247526,12 +247757,26 @@ ${source_default.cyan("2. Standard scenario set:")}
247526
247757
  - ${source_default.yellow("Failure")} (priority 2): Feature encountered error
247527
247758
  - ${source_default.dim("Fallback")} (priority 999): Generic execution captured
247528
247759
 
247529
- ${source_default.cyan("3. Template interpolation:")}
247530
- Use {{path.to.value}} to reference event attributes
247531
- Examples:
247532
- - {{record.count}}
247533
- - {{error.message}}
247534
- - {{result.invalidCount}}
247760
+ ${source_default.cyan("3. Template syntax (Handlebars):")}
247761
+ Templates use Handlebars syntax for dynamic content:
247762
+
247763
+ ${source_default.bold("Variables:")}
247764
+ - {{variable}} Simple variable
247765
+ - {{result.count}} Nested property
247766
+ - {{error.message}} Deeply nested
247767
+
247768
+ ${source_default.bold("Conditionals:")}
247769
+ - {{#if condition}}...{{/if}} If block
247770
+ - {{#if condition}}...{{else}}...{{/if}} If-else
247771
+ - {{#if (eq status "ok")}}\u2705{{else}}\u274C{{/if}} Comparison
247772
+
247773
+ ${source_default.bold("Loops:")}
247774
+ - {{#each items}}{{this}}{{/each}} Iterate array
247775
+ - {{#each items}}{{@index}}: {{this}}{{/each}} With index
247776
+
247777
+ ${source_default.bold("Comparison helpers:")}
247778
+ - eq, ne, lt, gt, lte, gte, and, or, not
247779
+ - Example: {{#if (gt count 10)}}Many{{/if}}
247535
247780
 
247536
247781
  ${source_default.cyan("4. Template style:")}
247537
247782
  - Clear, concise summary line
@@ -247657,11 +247902,20 @@ ${source_default.yellow(".principal-views/data-validator.otel.canvas")}
247657
247902
  "text": "# validation.started\\n\\nEmitted when validation begins",
247658
247903
  "x": 0, "y": 0, "width": 200, "height": 100,
247659
247904
  "pv": {
247660
- "otelEvent": {
247661
- "name": "validation.started",
247662
- "attributes": {
247663
- "required": ["input.recordCount"],
247664
- "optional": ["input.source"]
247905
+ "event": "validation.started",
247906
+ "sources": ["src/validator.ts"],
247907
+ "otel": {
247908
+ "kind": "event",
247909
+ "category": "lifecycle"
247910
+ },
247911
+ "dataSchema": {
247912
+ "input.recordCount": {
247913
+ "type": "number",
247914
+ "required": true
247915
+ },
247916
+ "input.source": {
247917
+ "type": "string",
247918
+ "required": false
247665
247919
  }
247666
247920
  }
247667
247921
  }
@@ -247672,11 +247926,24 @@ ${source_default.yellow(".principal-views/data-validator.otel.canvas")}
247672
247926
  "text": "# validation.complete\\n\\nEmitted when validation succeeds",
247673
247927
  "x": 250, "y": 0, "width": 200, "height": 100,
247674
247928
  "pv": {
247675
- "otelEvent": {
247676
- "name": "validation.complete",
247677
- "attributes": {
247678
- "required": ["result.validCount", "result.invalidCount"],
247679
- "optional": ["duration.ms"]
247929
+ "event": "validation.complete",
247930
+ "sources": ["src/validator.ts"],
247931
+ "otel": {
247932
+ "kind": "event",
247933
+ "category": "lifecycle"
247934
+ },
247935
+ "dataSchema": {
247936
+ "result.validCount": {
247937
+ "type": "number",
247938
+ "required": true
247939
+ },
247940
+ "result.invalidCount": {
247941
+ "type": "number",
247942
+ "required": true
247943
+ },
247944
+ "duration.ms": {
247945
+ "type": "number",
247946
+ "required": false
247680
247947
  }
247681
247948
  }
247682
247949
  }
@@ -247687,11 +247954,24 @@ ${source_default.yellow(".principal-views/data-validator.otel.canvas")}
247687
247954
  "text": "# validation.error\\n\\nEmitted when validation fails",
247688
247955
  "x": 500, "y": 0, "width": 200, "height": 100,
247689
247956
  "pv": {
247690
- "otelEvent": {
247691
- "name": "validation.error",
247692
- "attributes": {
247693
- "required": ["error.type", "error.message"],
247694
- "optional": ["error.stage"]
247957
+ "event": "validation.error",
247958
+ "sources": ["src/validator.ts"],
247959
+ "otel": {
247960
+ "kind": "event",
247961
+ "category": "error"
247962
+ },
247963
+ "dataSchema": {
247964
+ "error.type": {
247965
+ "type": "string",
247966
+ "required": true
247967
+ },
247968
+ "error.message": {
247969
+ "type": "string",
247970
+ "required": true
247971
+ },
247972
+ "error.stage": {
247973
+ "type": "string",
247974
+ "required": false
247695
247975
  }
247696
247976
  }
247697
247977
  }
@@ -249024,7 +249304,7 @@ function capitalize(str3) {
249024
249304
  // src/commands/narrative/validate.ts
249025
249305
  function createValidateCommand2() {
249026
249306
  const command = new Command("validate");
249027
- command.description("Validate narrative template syntax, schema, and references").argument("<file>", "Path to .narrative.json file").option("--canvas <path>", "Override canvas file path for validation").option("--json", "Output violations as JSON").option("-q, --quiet", "Only show errors, suppress warnings").option("-d, --dir <path>", "Project directory (default: cwd)").action(async (file, options) => {
249307
+ command.description("Validate narrative template syntax, schema, and references").argument("<file>", "Path to .narrative.json file").option("--canvas <path>", "Override canvas file path for validation").option("--execution <path>", "Execution file (.otel.json) for validating attribute references").option("--json", "Output violations as JSON").option("-q, --quiet", "Only show errors, suppress warnings").option("-d, --dir <path>", "Project directory (default: cwd)").action(async (file, options) => {
249028
249308
  try {
249029
249309
  const baseDir = options.dir || process.cwd();
249030
249310
  const narrativePath = resolvePath(file, baseDir);
@@ -249045,13 +249325,40 @@ function createValidateCommand2() {
249045
249325
  canvas = void 0;
249046
249326
  }
249047
249327
  }
249328
+ let executionData;
249329
+ if (options.execution) {
249330
+ try {
249331
+ const executionPath = resolvePath(options.execution, baseDir);
249332
+ const execution = await loadExecution(executionPath);
249333
+ const events = executionToEvents(execution);
249334
+ const aggregates = computeAggregates(events);
249335
+ const eventAttributes = /* @__PURE__ */ new Map();
249336
+ for (const event of events) {
249337
+ if (!eventAttributes.has(event.name)) {
249338
+ eventAttributes.set(event.name, {});
249339
+ }
249340
+ const attrs = eventAttributes.get(event.name);
249341
+ if (event.attributes) {
249342
+ Object.assign(attrs, event.attributes);
249343
+ }
249344
+ }
249345
+ executionData = { aggregates, eventAttributes };
249346
+ } catch (error) {
249347
+ console.error(
249348
+ source_default.yellow("Warning:"),
249349
+ `Failed to load execution file: ${error.message}`
249350
+ );
249351
+ console.error(source_default.gray(" Attribute validation will be skipped"));
249352
+ }
249353
+ }
249048
249354
  const validator = new NarrativeValidator();
249049
249355
  const context = {
249050
249356
  narrative,
249051
249357
  narrativePath,
249052
249358
  canvasPath,
249053
249359
  canvas,
249054
- basePath: baseDir
249360
+ basePath: baseDir,
249361
+ executionData
249055
249362
  };
249056
249363
  const result = await validator.validate(context);
249057
249364
  const violations = options.quiet ? result.violations.filter((v) => v.severity === "error") : result.violations;
@@ -249075,7 +249382,8 @@ function createValidateCommand2() {
249075
249382
  errors: errors.length,
249076
249383
  warnings: warnings.length,
249077
249384
  scenarioCount: narrative.scenarios.length,
249078
- hasDefault: narrative.scenarios.some((s) => s.condition.default)
249385
+ hasDefault: narrative.scenarios.some((s) => s.condition.default),
249386
+ attributeValidation: executionData ? "enabled" : "skipped"
249079
249387
  }
249080
249388
  };
249081
249389
  console.log(JSON.stringify(output, null, 2));
@@ -249140,6 +249448,17 @@ ${icon} ${severity}: ${violation.message}`);
249140
249448
  if (canvasPath) {
249141
249449
  console.log(source_default.gray(` \u2022 Canvas: ${narrative.canvas || canvasPath}`));
249142
249450
  }
249451
+ if (executionData) {
249452
+ console.log(
249453
+ source_default.gray(" \u2022 Attribute validation:"),
249454
+ source_default.green("enabled")
249455
+ );
249456
+ } else {
249457
+ console.log(
249458
+ source_default.gray(" \u2022 Attribute validation:"),
249459
+ source_default.gray("skipped (use --execution to enable)")
249460
+ );
249461
+ }
249143
249462
  console.log();
249144
249463
  }
249145
249464
  if (errors.length > 0) {