@unified-product-graph/core 0.10.3 → 0.10.5

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,13 +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 },
1361
1390
  // 0.10.3: the polymorphic, type-agnostic sibling. ANY node (a feature, a
1362
1391
  // product, a market_segment) classified against a classification_value, so
1363
1392
  // classification is not welded to the competitor type. Also dual-registered as
1364
1393
  // a cross-edge for the registry-canonical case. source_type:'node' (wildcard);
1365
1394
  // listed in UPG_POLYMORPHIC_EDGE_KEYS.
1366
- node_classified_as_classification_value: { forward_verb: "classified_as", reverse_verb: "classification_of", classification: "semantic", source_type: "node", target_type: "classification_value" },
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 },
1367
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" },
1368
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" },
1369
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" },
@@ -3217,6 +3246,10 @@ function edgeCarriesProperties(type) {
3217
3246
  const def = UPG_EDGE_CATALOG[type];
3218
3247
  return def?.carries_properties === true;
3219
3248
  }
3249
+ function getEdgePropertySchema(type) {
3250
+ const def = UPG_EDGE_CATALOG[type];
3251
+ return def?.property_schema;
3252
+ }
3220
3253
 
3221
3254
  // src/catalog/legacy-product-stages.ts
3222
3255
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -18747,6 +18780,65 @@ function isUPGDocument(doc) {
18747
18780
  return validateUPGDocument(doc).valid;
18748
18781
  }
18749
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
+
18750
18842
  // src/presentation/entity-emoji.ts
18751
18843
  var DEFAULT_ENTITY_EMOJI = "\u{1F4E6}";
18752
18844
  var ENTITY_EMOJI = {
@@ -28646,7 +28738,7 @@ function isCanonical(text) {
28646
28738
  }
28647
28739
 
28648
28740
  // src/index.ts
28649
- var UPG_VERSION = "0.10.3";
28741
+ var UPG_VERSION = "0.10.5";
28650
28742
  var UPG_FORMAT_VERSION = "0.4.0";
28651
28743
  var MARKDOWN_FORMAT_VERSION = "0.1";
28652
28744
  var UPG_TYPES = getTypes();
@@ -28715,6 +28807,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28715
28807
  BUSINESS_GROWTH_METRIC_DRIVEN_PLAYBOOK,
28716
28808
  BUSINESS_GTM_GROWTH_PLAYBOOK,
28717
28809
  BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
28810
+ CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
28718
28811
  CONTENT_DEPTH_WARNING_RULES,
28719
28812
  DEFAULT_ENTITY_EMOJI,
28720
28813
  DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
@@ -28826,6 +28919,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28826
28919
  getDomainForType,
28827
28920
  getDomainIdForType,
28828
28921
  getDomainsInRing,
28922
+ getEdgePropertySchema,
28829
28923
  getEnumScale,
28830
28924
  getEnumValueMeta,
28831
28925
  getGuideForDomain,
@@ -28890,6 +28984,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
28890
28984
  ringOrderedDomainIds,
28891
28985
  rotateSlug,
28892
28986
  serializeCanonical,
28987
+ validateEdgeProperties,
28893
28988
  validateProductStageStrict,
28894
28989
  validateUPGDocument,
28895
28990
  validateUPGFramework,