@unified-product-graph/core 0.10.2 → 0.10.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/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  BUSINESS_GROWTH_METRIC_DRIVEN_PLAYBOOK: () => BUSINESS_GROWTH_METRIC_DRIVEN_PLAYBOOK,
25
25
  BUSINESS_GTM_GROWTH_PLAYBOOK: () => BUSINESS_GTM_GROWTH_PLAYBOOK,
26
26
  BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK: () => BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
27
+ CLASSIFICATION_EDGE_PROPERTY_SCHEMA: () => CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
27
28
  CONTENT_DEPTH_WARNING_RULES: () => CONTENT_DEPTH_WARNING_RULES,
28
29
  DEFAULT_ENTITY_EMOJI: () => DEFAULT_ENTITY_EMOJI,
29
30
  DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK: () => DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
@@ -135,6 +136,7 @@ __export(index_exports, {
135
136
  getDomainForType: () => getDomainForType,
136
137
  getDomainIdForType: () => getDomainIdForType,
137
138
  getDomainsInRing: () => getDomainsInRing,
139
+ getEdgePropertySchema: () => getEdgePropertySchema,
138
140
  getEnumScale: () => getEnumScale,
139
141
  getEnumValueMeta: () => getEnumValueMeta,
140
142
  getGuideForDomain: () => getGuideForDomain,
@@ -199,6 +201,7 @@ __export(index_exports, {
199
201
  ringOrderedDomainIds: () => ringOrderedDomainIds,
200
202
  rotateSlug: () => rotateSlug,
201
203
  serializeCanonical: () => serializeCanonical,
204
+ validateEdgeProperties: () => validateEdgeProperties,
202
205
  validateProductStageStrict: () => validateProductStageStrict,
203
206
  validateUPGDocument: () => validateUPGDocument,
204
207
  validateUPGFramework: () => validateUPGFramework,
@@ -1185,6 +1188,32 @@ function getTypeName(typeId) {
1185
1188
 
1186
1189
  // src/catalog/edge-catalog.ts
1187
1190
  var UPG_WILDCARD_ENDPOINT = "node";
1191
+ var CLASSIFICATION_EDGE_PROPERTY_SCHEMA = {
1192
+ confidence: {
1193
+ type: "assessment",
1194
+ scale_id: "confidence_5",
1195
+ description: "How sure we are this node belongs in this classification cell, on the canonical confidence_5 scale. Numeric value plus a high/medium/low label.",
1196
+ properties: {
1197
+ value: { type: "number", description: "Numeric value 1-5 (confidence_5)." },
1198
+ label: { type: "string", description: "Qualitative label (Guessing/Hunch/Some evidence/Confident/Data-backed, or high/medium/low)." },
1199
+ scale_id: { type: "string", description: "Scale rated on (optional; defaults confidence_5)." },
1200
+ normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
1201
+ },
1202
+ required: ["value", "label"]
1203
+ },
1204
+ assessed_on: {
1205
+ type: "string",
1206
+ description: 'Provenance: ISO date-time the classification was made or last re-checked. Drives staleness queries. @example "2026-06-13"'
1207
+ },
1208
+ rationale: {
1209
+ type: "string",
1210
+ description: "Optional. Short note on why this node sits in this cell. Edge-level, distinct from classification_value.rationale (why the value exists as a category)."
1211
+ },
1212
+ evidence: {
1213
+ type: "string",
1214
+ description: "Optional. A source URL, or a competitor_signal / evidence node id backing the classification. Mirrors feature_rivals_competitor_feature.evidence (free text or a node id)."
1215
+ }
1216
+ };
1188
1217
  var UPG_EDGE_CATALOG = {
1189
1218
  // ── Part 1: Core Narrative Spine (Ring 1) ──────────────────────────────────
1190
1219
  // 1.1 User Domain
@@ -1357,7 +1386,13 @@ var UPG_EDGE_CATALOG = {
1357
1386
  // competitor can be classified directly against a `registry/{classification_value}`
1358
1387
  // canonical, eliminating the per-graph taxonomy node. Within-graph (the catalogue
1359
1388
  // case) here; cross-product against the registry canonical via create_cross_product_edge.
1360
- competitor_classified_as_classification_value: { forward_verb: "classified_as", reverse_verb: "classification_of", classification: "semantic", source_type: "competitor", target_type: "classification_value" },
1389
+ competitor_classified_as_classification_value: { forward_verb: "classified_as", reverse_verb: "classification_of", classification: "semantic", source_type: "competitor", target_type: "classification_value", carries_properties: true, property_schema: CLASSIFICATION_EDGE_PROPERTY_SCHEMA },
1390
+ // 0.10.3: the polymorphic, type-agnostic sibling. ANY node (a feature, a
1391
+ // product, a market_segment) classified against a classification_value, so
1392
+ // classification is not welded to the competitor type. Also dual-registered as
1393
+ // a cross-edge for the registry-canonical case. source_type:'node' (wildcard);
1394
+ // listed in UPG_POLYMORPHIC_EDGE_KEYS.
1395
+ node_classified_as_classification_value: { forward_verb: "classified_as", reverse_verb: "classification_of", classification: "semantic", source_type: "node", target_type: "classification_value", carries_properties: true, property_schema: CLASSIFICATION_EDGE_PROPERTY_SCHEMA },
1361
1396
  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" },
1362
1397
  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" },
1363
1398
  persona_anti_fit_for_competitor: { forward_verb: "is_anti_fit_for", reverse_verb: "should_not_be_picked_by", classification: "semantic", source_type: "persona", target_type: "competitor" },
@@ -3195,7 +3230,9 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
3195
3230
  // Universal architecture references
3196
3231
  "node_belongs_to_bounded_context",
3197
3232
  // Framework exercises (an exercise can include any entity type)
3198
- "framework_exercise_includes_node"
3233
+ "framework_exercise_includes_node",
3234
+ // Universal classification (any node classified against a classification_value)
3235
+ "node_classified_as_classification_value"
3199
3236
  ];
3200
3237
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
3201
3238
  function isPolymorphicEdge(key) {
@@ -3209,6 +3246,10 @@ function edgeCarriesProperties(type) {
3209
3246
  const def = UPG_EDGE_CATALOG[type];
3210
3247
  return def?.carries_properties === true;
3211
3248
  }
3249
+ function getEdgePropertySchema(type) {
3250
+ const def = UPG_EDGE_CATALOG[type];
3251
+ return def?.property_schema;
3252
+ }
3212
3253
 
3213
3254
  // src/catalog/legacy-product-stages.ts
3214
3255
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -3253,7 +3294,8 @@ var UPG_CROSS_EDGE_TYPES = [
3253
3294
  "feature_rivals_competitor_feature",
3254
3295
  "competitor_signal_maps_to_feature",
3255
3296
  "competitor_signal_surfaces_opportunity",
3256
- "competitor_classified_as_classification_value"
3297
+ "competitor_classified_as_classification_value",
3298
+ "node_classified_as_classification_value"
3257
3299
  ];
3258
3300
  var REGISTRY_PRODUCT_ID = "registry";
3259
3301
 
@@ -18738,6 +18780,65 @@ function isUPGDocument(doc) {
18738
18780
  return validateUPGDocument(doc).valid;
18739
18781
  }
18740
18782
 
18783
+ // src/properties/edge-property-validation.ts
18784
+ function validateEdgeProperties(edgeType, properties) {
18785
+ const schema = getEdgePropertySchema(edgeType);
18786
+ if (!schema || !properties) return [];
18787
+ const errors = [];
18788
+ const allowed = Object.keys(schema);
18789
+ for (const key of Object.keys(properties)) {
18790
+ if (!allowed.includes(key)) {
18791
+ errors.push(`unknown property "${key}" for edge type "${edgeType}" (allowed: ${allowed.join(", ")})`);
18792
+ }
18793
+ }
18794
+ for (const [key, def] of Object.entries(schema)) {
18795
+ const val = properties[key];
18796
+ if (val === void 0 || val === null) continue;
18797
+ errors.push(...validateValue(key, def, val));
18798
+ }
18799
+ return errors;
18800
+ }
18801
+ function validateValue(key, def, val) {
18802
+ const errors = [];
18803
+ switch (def.type) {
18804
+ case "string":
18805
+ if (typeof val !== "string") errors.push(`property "${key}" must be a string`);
18806
+ break;
18807
+ case "number":
18808
+ if (typeof val !== "number") errors.push(`property "${key}" must be a number`);
18809
+ break;
18810
+ case "boolean":
18811
+ if (typeof val !== "boolean") errors.push(`property "${key}" must be a boolean`);
18812
+ break;
18813
+ case "assessment": {
18814
+ if (typeof val !== "object" || Array.isArray(val)) {
18815
+ errors.push(`property "${key}" must be an assessment object with value and label`);
18816
+ break;
18817
+ }
18818
+ const a = val;
18819
+ for (const r of def.required ?? ["value", "label"]) {
18820
+ if (a[r] === void 0 || a[r] === null) errors.push(`assessment "${key}" is missing required "${r}"`);
18821
+ }
18822
+ if (a.value !== void 0 && a.value !== null) {
18823
+ if (typeof a.value !== "number") {
18824
+ errors.push(`assessment "${key}".value must be a number`);
18825
+ } else {
18826
+ const scaleId = (typeof a.scale_id === "string" ? a.scale_id : void 0) ?? def.scale_id;
18827
+ const scale = scaleId ? getScale(scaleId) : void 0;
18828
+ if (scale && (a.value < scale.min || a.value > scale.max)) {
18829
+ errors.push(`assessment "${key}".value ${a.value} is out of range ${scale.min}-${scale.max} for scale ${scaleId}`);
18830
+ }
18831
+ }
18832
+ }
18833
+ if (typeof a.scale_id === "string" && def.scale_id !== void 0 && a.scale_id !== def.scale_id) {
18834
+ errors.push(`assessment "${key}".scale_id "${a.scale_id}" must be "${def.scale_id}"`);
18835
+ }
18836
+ break;
18837
+ }
18838
+ }
18839
+ return errors;
18840
+ }
18841
+
18741
18842
  // src/presentation/entity-emoji.ts
18742
18843
  var DEFAULT_ENTITY_EMOJI = "\u{1F4E6}";
18743
18844
  var ENTITY_EMOJI = {
@@ -28637,7 +28738,7 @@ function isCanonical(text) {
28637
28738
  }
28638
28739
 
28639
28740
  // src/index.ts
28640
- var UPG_VERSION = "0.10.2";
28741
+ var UPG_VERSION = "0.10.4";
28641
28742
  var UPG_FORMAT_VERSION = "0.4.0";
28642
28743
  var MARKDOWN_FORMAT_VERSION = "0.1";
28643
28744
  var UPG_TYPES = getTypes();
@@ -28706,6 +28807,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28706
28807
  BUSINESS_GROWTH_METRIC_DRIVEN_PLAYBOOK,
28707
28808
  BUSINESS_GTM_GROWTH_PLAYBOOK,
28708
28809
  BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
28810
+ CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
28709
28811
  CONTENT_DEPTH_WARNING_RULES,
28710
28812
  DEFAULT_ENTITY_EMOJI,
28711
28813
  DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
@@ -28817,6 +28919,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28817
28919
  getDomainForType,
28818
28920
  getDomainIdForType,
28819
28921
  getDomainsInRing,
28922
+ getEdgePropertySchema,
28820
28923
  getEnumScale,
28821
28924
  getEnumValueMeta,
28822
28925
  getGuideForDomain,
@@ -28881,6 +28984,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28881
28984
  ringOrderedDomainIds,
28882
28985
  rotateSlug,
28883
28986
  serializeCanonical,
28987
+ validateEdgeProperties,
28884
28988
  validateProductStageStrict,
28885
28989
  validateUPGDocument,
28886
28990
  validateUPGFramework,