@unified-product-graph/core 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1353,6 +1353,10 @@ var UPG_EDGE_CATALOG = {
1353
1353
  // would be a separate catalog-mechanic change).
1354
1354
  competitive_analysis_dimensioned_by_classification_axis: { forward_verb: "dimensioned_by", reverse_verb: "dimensions", classification: "hierarchy", source_type: "competitive_analysis", target_type: "classification_axis" },
1355
1355
  classification_axis_includes_classification_value: { forward_verb: "includes", reverse_verb: "value_of", classification: "hierarchy", source_type: "classification_axis", target_type: "classification_value" },
1356
+ // 0.10.2: also dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so a
1357
+ // competitor can be classified directly against a `registry/{classification_value}`
1358
+ // canonical, eliminating the per-graph taxonomy node. Within-graph (the catalogue
1359
+ // case) here; cross-product against the registry canonical via create_cross_product_edge.
1356
1360
  competitor_classified_as_classification_value: { forward_verb: "classified_as", reverse_verb: "classification_of", classification: "semantic", source_type: "competitor", target_type: "classification_value" },
1357
1361
  persona_anti_fit_for_classification_value: { forward_verb: "is_anti_fit_for", reverse_verb: "should_not_be_picked_by", classification: "semantic", source_type: "persona", target_type: "classification_value" },
1358
1362
  persona_anti_fit_for_product: { forward_verb: "is_anti_fit_for", reverse_verb: "should_not_be_picked_by", classification: "semantic", source_type: "persona", target_type: "product" },
@@ -3248,7 +3252,8 @@ var UPG_CROSS_EDGE_TYPES = [
3248
3252
  "primitive_stored_as_data_type",
3249
3253
  "feature_rivals_competitor_feature",
3250
3254
  "competitor_signal_maps_to_feature",
3251
- "competitor_signal_surfaces_opportunity"
3255
+ "competitor_signal_surfaces_opportunity",
3256
+ "competitor_classified_as_classification_value"
3252
3257
  ];
3253
3258
  var REGISTRY_PRODUCT_ID = "registry";
3254
3259
 
@@ -7458,6 +7463,34 @@ function getDeprecatedTypes() {
7458
7463
  return deprecated;
7459
7464
  }
7460
7465
  var UPG_PROPERTY_MIGRATIONS = {
7466
+ // ── v0.10.2: market_intelligence number -> assessment reshape ──────────────
7467
+ //
7468
+ // The v0.10.0 property-registry generator fix (#43) corrected several property
7469
+ // types that interfaces already declared as `UPGAssessment` but the generator
7470
+ // had emitted loosely. A pre-0.10.0 graph that stored a bare number for
7471
+ // `market_trend.impact` / `relevance` is now invalid (the schema wants
7472
+ // `{value,label}`). These rules wrap the bare number on its canonical scale so
7473
+ // the migration is one command, not a hand-reshape. The enum tightenings the
7474
+ // same #43 fix surfaced (data_flow.direction, integration_pattern.pattern_type,
7475
+ // api_contract.protocol, service.service_type) already have rules under 0.9.12;
7476
+ // 0.10.2 also makes ALL of them visible to validate_graph (they were applied by
7477
+ // migrate_properties but never surfaced as a fixable drift).
7478
+ "0.10.2": [
7479
+ {
7480
+ kind: "reshape_value_to_assessment",
7481
+ type: "market_trend",
7482
+ property: "impact",
7483
+ scale_id: "impact_5",
7484
+ reason: "market_trend.impact is a UPGAssessment (impact_5); the 0.10.0 generator fix (#43) tightened it from a bare number. A legacy numeric value wraps to {value,label} on impact_5."
7485
+ },
7486
+ {
7487
+ kind: "reshape_value_to_assessment",
7488
+ type: "market_trend",
7489
+ property: "relevance",
7490
+ scale_id: "importance_5",
7491
+ reason: "market_trend.relevance is a UPGAssessment (importance_5); the 0.10.0 generator fix (#43) tightened it from a bare number. A legacy numeric value wraps to {value,label} on importance_5."
7492
+ }
7493
+ ],
7461
7494
  // ── v0.9.14: drop the key_result kr_status / status twin (A2) ──────────────
7462
7495
  //
7463
7496
  // key_result carried BOTH a lifecycle `status` and a `kr_status` property with
@@ -8030,6 +8063,26 @@ function migrateNodeProperties(node, fromVersion, toVersion) {
8030
8063
  mutated = true;
8031
8064
  break;
8032
8065
  }
8066
+ case "reshape_value_to_assessment": {
8067
+ const props = workingNode.properties;
8068
+ if (!props || !(m.property in props)) break;
8069
+ const oldValue = props[m.property];
8070
+ let num = null;
8071
+ if (typeof oldValue === "number" && Number.isFinite(oldValue)) num = oldValue;
8072
+ else if (typeof oldValue === "string" && oldValue.trim() !== "" && Number.isFinite(Number(oldValue))) {
8073
+ num = Number(oldValue);
8074
+ }
8075
+ if (num === null) break;
8076
+ const label = getScale(m.scale_id)?.points.find((p) => p.value === num)?.label ?? String(num);
8077
+ const nextProps = {
8078
+ ...props,
8079
+ [m.property]: { value: num, label, scale_id: m.scale_id }
8080
+ };
8081
+ workingNode.properties = nextProps;
8082
+ mutated = true;
8083
+ changes.push({ kind: "reshaped_to_assessment", property: m.property, scale_id: m.scale_id, value: num, label });
8084
+ break;
8085
+ }
8033
8086
  }
8034
8087
  }
8035
8088
  }
@@ -18250,6 +18303,7 @@ for (const fw of UPG_FRAMEWORKS) {
18250
18303
 
18251
18304
  // src/grammar/validate.ts
18252
18305
  var crossProductEdgeTypeSet = new Set(UPG_CROSS_EDGE_TYPES);
18306
+ var withinProductEdgeTypeSet = new Set(Object.keys(UPG_EDGE_CATALOG));
18253
18307
  var CONTENT_DEPTH_WARNING_RULES = /* @__PURE__ */ new Set([
18254
18308
  "property-type",
18255
18309
  "property-enum",
@@ -18352,7 +18406,7 @@ function validateUPGDocument(doc) {
18352
18406
  if (!e.id || typeof e.id !== "string") {
18353
18407
  errors.push({ path: `${path}.id`, message: "Edge id is required and must be a string" });
18354
18408
  }
18355
- if (typeof e.type === "string" && crossProductEdgeTypeSet.has(e.type)) {
18409
+ if (typeof e.type === "string" && crossProductEdgeTypeSet.has(e.type) && !withinProductEdgeTypeSet.has(e.type)) {
18356
18410
  errors.push({
18357
18411
  path: `${path}.type`,
18358
18412
  message: `Cross-product edge type "${e.type}" must live in portfolio.cross_edges[], not product edges[].`
@@ -28583,7 +28637,7 @@ function isCanonical(text) {
28583
28637
  }
28584
28638
 
28585
28639
  // src/index.ts
28586
- var UPG_VERSION = "0.10.0";
28640
+ var UPG_VERSION = "0.10.2";
28587
28641
  var UPG_FORMAT_VERSION = "0.4.0";
28588
28642
  var MARKDOWN_FORMAT_VERSION = "0.1";
28589
28643
  var UPG_TYPES = getTypes();