@unified-product-graph/core 0.16.1 → 0.17.0

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
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK: () => BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
37
37
  CLASSIFICATION_EDGE_PROPERTY_SCHEMA: () => CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
38
38
  CONTENT_DEPTH_WARNING_RULES: () => CONTENT_DEPTH_WARNING_RULES,
39
+ COVERAGE_ANTI_PATTERNS: () => COVERAGE_ANTI_PATTERNS,
39
40
  DEFAULT_ENTITY_EMOJI: () => DEFAULT_ENTITY_EMOJI,
40
41
  DEFERRED_PROPOSED_BY_CONTRACT: () => DEFERRED_PROPOSED_BY_CONTRACT,
41
42
  DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK: () => DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
@@ -55,8 +56,10 @@ __export(index_exports, {
55
56
  REFLECT_MODES: () => REFLECT_MODES,
56
57
  REGISTRY_PRODUCT_ID: () => REGISTRY_PRODUCT_ID,
57
58
  STRATEGY_OUTCOMES_PLAYBOOK: () => STRATEGY_OUTCOMES_PLAYBOOK,
59
+ THIN_GRAPH_THRESHOLD: () => THIN_GRAPH_THRESHOLD,
58
60
  UPG_ACTIVE_TYPES: () => UPG_ACTIVE_TYPES,
59
61
  UPG_ANTI_PATTERNS: () => UPG_ANTI_PATTERNS,
62
+ UPG_ANTI_PATTERN_CONCERNS: () => UPG_ANTI_PATTERN_CONCERNS,
60
63
  UPG_APPROACHES: () => UPG_APPROACHES,
61
64
  UPG_APPROACHES_BY_ID: () => UPG_APPROACHES_BY_ID,
62
65
  UPG_AREA_TAXONOMY: () => UPG_AREA_TAXONOMY,
@@ -117,6 +120,7 @@ __export(index_exports, {
117
120
  UPG_TYPE_LABELS: () => UPG_TYPE_LABELS,
118
121
  UPG_TYPE_LABELS_MAP: () => UPG_TYPE_LABELS_MAP,
119
122
  UPG_TYPE_NAMES: () => UPG_TYPE_NAMES,
123
+ UPG_VALIDATION_PROFILES: () => UPG_VALIDATION_PROFILES,
120
124
  UPG_VALID_CHILDREN: () => UPG_VALID_CHILDREN,
121
125
  UPG_VERSION: () => UPG_VERSION,
122
126
  UPG_WILDCARD_ENDPOINT: () => UPG_WILDCARD_ENDPOINT,
@@ -129,6 +133,9 @@ __export(index_exports, {
129
133
  collectSlugsForType: () => collectSlugsForType,
130
134
  compareVersions: () => compareVersions,
131
135
  computeBodyChecksum: () => computeBodyChecksum,
136
+ concernEvaluatedFor: () => concernEvaluatedFor,
137
+ concernFor: () => concernFor,
138
+ concernGatesFor: () => concernGatesFor,
132
139
  describeTreePattern: () => describeTreePattern,
133
140
  edgeCarriesProperties: () => edgeCarriesProperties,
134
141
  evaluateAntiPatterns: () => evaluateAntiPatterns,
@@ -203,6 +210,7 @@ __export(index_exports, {
203
210
  isRegisteredPolymorphicEdge: () => isRegisteredPolymorphicEdge,
204
211
  isRuntimeStateShapedProperty: () => isRuntimeStateShapedProperty,
205
212
  isSubSequenceStep: () => isSubSequenceStep,
213
+ isThinCoverageAdvisory: () => isThinCoverageAdvisory,
206
214
  isUPGDocument: () => isUPGDocument,
207
215
  listPropertiesByModifier: () => listPropertiesByModifier,
208
216
  listStatusMigrations: () => listStatusMigrations,
@@ -228,6 +236,7 @@ __export(index_exports, {
228
236
  validateProductStageStrict: () => validateProductStageStrict,
229
237
  validateUPGDocument: () => validateUPGDocument,
230
238
  validateUPGFramework: () => validateUPGFramework,
239
+ validationProfileFor: () => validationProfileFor,
231
240
  walkMigrationChainToCanonical: () => walkMigrationChainToCanonical
232
241
  });
233
242
  module.exports = __toCommonJS(index_exports);
@@ -3408,7 +3417,10 @@ var UPG_CROSS_EDGE_TYPES = [
3408
3417
  "screen_targets_competitor",
3409
3418
  "feature_surfaces_product",
3410
3419
  "feature_uses_design_component",
3411
- "product_implements_design_system"
3420
+ "product_implements_design_system",
3421
+ // Org-link (0.17.0): dual-registered ownership edges (see UPGCrossEdgeType above).
3422
+ "node_owned_by_team",
3423
+ "node_owned_by_department"
3412
3424
  ];
3413
3425
  var REGISTRY_PRODUCT_ID = "registry";
3414
3426
 
@@ -26529,6 +26541,54 @@ var UPG_ANTI_PATTERNS = [
26529
26541
  severity: "medium",
26530
26542
  source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (product discovery)" }
26531
26543
  },
26544
+ // ── Operating-function layer (0.17.0): member_kind: operating_function ─────
26545
+ // Carry concern 'operating' (UPG_ANTI_PATTERN_CONCERNS) and are evaluated ONLY
26546
+ // for operating_function graphs — a function a team operates (revenue / success
26547
+ // / finance / people / marketing), not a product it ships. The product-spine
26548
+ // patterns above are not evaluated for that kind (category errors); these assert
26549
+ // the function spine instead.
26550
+ {
26551
+ id: "operating-function-without-north-star",
26552
+ since: "0.17.0",
26553
+ name: "Operating function without a north-star metric",
26554
+ description: "An operating function graph has real content but no north-star metric to operate toward. A function (sales, finance, people, marketing) is run against one headline number it moves and that rolls up to the company tree. With no north-star metric the function has direction but nothing to steer by.",
26555
+ structured_condition: {
26556
+ operator: "and",
26557
+ checks: [
26558
+ { check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
26559
+ { check: { type: "entity_count", entity_type: "metric", filter: { property: "designation", value: "north_star" }, comparison: "zero" } }
26560
+ ]
26561
+ },
26562
+ why_it_matters: "A function with no north-star metric cannot be steered, prioritised, or rolled into the company metric tree. Operating work becomes activity without a measured target.",
26563
+ remediation: "Add the one `metric` the function operates toward and mark it `designation: north_star`, then wire it to the company metric tree via `metric_decomposes_into_metric`.",
26564
+ stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
26565
+ severity: "high",
26566
+ source: { kind: "fundamental" }
26567
+ },
26568
+ {
26569
+ id: "operating-function-without-operating-content",
26570
+ since: "0.17.0",
26571
+ name: "Operating function without operating content",
26572
+ description: "An operating function graph has more than a few entities but none in any operating domain (sales, go-to-market, customer success, growth, marketing, business model, pricing). It carries direction and people but no operating substance: the work the function actually runs.",
26573
+ structured_condition: {
26574
+ operator: "and",
26575
+ checks: [
26576
+ { check: { type: "total_entity_count", comparison: "gt", threshold: 3 } },
26577
+ { check: { type: "domain_population", domain_id: "sales", comparison: "zero" } },
26578
+ { check: { type: "domain_population", domain_id: "go_to_market", comparison: "zero" } },
26579
+ { check: { type: "domain_population", domain_id: "customer_success", comparison: "zero" } },
26580
+ { check: { type: "domain_population", domain_id: "growth", comparison: "zero" } },
26581
+ { check: { type: "domain_population", domain_id: "marketing", comparison: "zero" } },
26582
+ { check: { type: "domain_population", domain_id: "business_model", comparison: "zero" } },
26583
+ { check: { type: "domain_population", domain_id: "pricing", comparison: "zero" } }
26584
+ ]
26585
+ },
26586
+ why_it_matters: "A function graph that is all strategy and org chart with no operating domain is a stub: it states intent but models none of the work the function runs, so it answers no operating questions.",
26587
+ remediation: "Populate at least one operating domain appropriate to the function (e.g. Field \u2192 `sales` / `go_to_market` / `customer_success`; Finance \u2192 `business_model` / `pricing`). Start from the matching template via `/upg-new-from-template`.",
26588
+ stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
26589
+ severity: "medium",
26590
+ source: { kind: "fundamental" }
26591
+ },
26532
26592
  // ── Foundations layer (0.9.13): portfolio-scoped, registry-aware ──────────
26533
26593
  // These read the shared registry + cross-product edges, so they are evaluated
26534
26594
  // by portfolio_validate, not the single-graph evaluator. They carry no
@@ -26569,6 +26629,23 @@ var UPG_ANTI_PATTERNS = [
26569
26629
  stages: ["build", "beta", "launch", "growth", "mature"],
26570
26630
  severity: "low",
26571
26631
  source: { kind: "fundamental" }
26632
+ },
26633
+ // ── Operating-function org link (0.17.0): portfolio-scoped ─────────────────
26634
+ // Cross-product detector (in portfolio_validate): an operating_function graph
26635
+ // should reference the org unit it operates under, which lives once in the
26636
+ // rollup's team_org map. Carries no structured_condition — the org link is a
26637
+ // cross-product edge in portfolio.upg, not a single-graph shape.
26638
+ {
26639
+ id: "operating-function-without-org-link",
26640
+ since: "0.17.0",
26641
+ scope: "portfolio",
26642
+ name: "Operating function without an org link",
26643
+ description: "An operating_function graph does not reference the org unit it operates under. The department/team hierarchy lives once in the rollup (org_rollup) as team_org entities; a function should point its spine at the department or team that owns it (node_owned_by_department / node_owned_by_team), so the operating layer hangs off the canonical org map rather than re-stating it.",
26644
+ why_it_matters: "A function with no org link floats free of the org chart: its work cannot be rolled up by department, and the single source of truth for who owns what is bypassed.",
26645
+ remediation: "Add a cross-product node_owned_by_department (or node_owned_by_team) edge from the function spine to its department/team in the rollup. Mint the org unit once in the org_rollup graph; functions reference it.",
26646
+ stages: ["concept", "validation", "build", "beta", "launch", "growth", "mature", "maintenance", "sunset"],
26647
+ severity: "medium",
26648
+ source: { kind: "fundamental" }
26572
26649
  }
26573
26650
  ];
26574
26651
  function getAntiPatternById(id) {
@@ -26581,6 +26658,45 @@ function getAntiPatternsBySeverity(severity) {
26581
26658
  return UPG_ANTI_PATTERNS.filter((ap) => ap.severity === severity);
26582
26659
  }
26583
26660
 
26661
+ // src/intelligence/validation-profiles.ts
26662
+ var UPG_ANTI_PATTERN_CONCERNS = {
26663
+ // universal — kind-independent graph hygiene + strategy discipline
26664
+ "objective-without-key-results": "universal",
26665
+ "single-domain-graph": "universal",
26666
+ "orphan-loose-thoughts": "universal",
26667
+ // operating — only evaluated for operating_function graphs
26668
+ "operating-function-without-north-star": "operating",
26669
+ "operating-function-without-operating-content": "operating"
26670
+ // everything else defaults to 'product_spine'
26671
+ };
26672
+ function concernFor(antiPatternId) {
26673
+ return UPG_ANTI_PATTERN_CONCERNS[antiPatternId] ?? "product_spine";
26674
+ }
26675
+ var UPG_VALIDATION_PROFILES = {
26676
+ product: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["product_spine", "universal"] },
26677
+ watched: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: [] },
26678
+ org_rollup: { evaluate_concerns: ["product_spine", "universal"], gating_concerns: ["universal"] },
26679
+ operating_function: { evaluate_concerns: ["universal", "operating"], gating_concerns: ["universal", "operating"] }
26680
+ };
26681
+ function validationProfileFor(kind) {
26682
+ return UPG_VALIDATION_PROFILES[kind ?? "product"] ?? UPG_VALIDATION_PROFILES.product;
26683
+ }
26684
+ function concernEvaluatedFor(kind, concern) {
26685
+ return validationProfileFor(kind).evaluate_concerns.includes(concern);
26686
+ }
26687
+ function concernGatesFor(kind, concern) {
26688
+ return validationProfileFor(kind).gating_concerns.includes(concern);
26689
+ }
26690
+ var COVERAGE_ANTI_PATTERNS = /* @__PURE__ */ new Set([
26691
+ "single-domain-graph",
26692
+ "persona-count-below-stage-benchmark",
26693
+ "competitors-missing-past-validation"
26694
+ ]);
26695
+ var THIN_GRAPH_THRESHOLD = 8;
26696
+ function isThinCoverageAdvisory(antiPatternId, totalEntityCount) {
26697
+ return COVERAGE_ANTI_PATTERNS.has(antiPatternId) && totalEntityCount < THIN_GRAPH_THRESHOLD;
26698
+ }
26699
+
26584
26700
  // src/intelligence/evaluator.ts
26585
26701
  var SEVERITY_ORDER = {
26586
26702
  high: 0,
@@ -26598,8 +26714,10 @@ function evaluateAntiPatterns(inputs, options) {
26598
26714
  if (inputs.productStage && !ap.stages.includes(inputs.productStage)) {
26599
26715
  continue;
26600
26716
  }
26717
+ const concern = concernFor(ap.id);
26718
+ if (!concernEvaluatedFor(inputs.memberKind, concern)) continue;
26601
26719
  if (evaluateCondition(ap.structured_condition, inputs)) {
26602
- fires.push(buildViolation(ap));
26720
+ fires.push(buildViolation(ap, concern));
26603
26721
  }
26604
26722
  }
26605
26723
  fires.sort((a, b) => {
@@ -26668,11 +26786,12 @@ function compareNumber(value, comparison, threshold) {
26668
26786
  }
26669
26787
  }
26670
26788
  function evaluateEntityCount(check, inputs) {
26671
- const statusFilter = check.filter && typeof check.filter.status === "string" ? check.filter.status : void 0;
26789
+ const filter = check.filter;
26672
26790
  let count = 0;
26673
- if (statusFilter) {
26674
- const byStatus = inputs.countsByTypeAndStatus?.[check.entity_type];
26675
- count = byStatus?.[statusFilter] ?? 0;
26791
+ if (filter && typeof filter.property === "string" && typeof filter.value === "string") {
26792
+ count = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.property]?.[filter.value] ?? 0;
26793
+ } else if (filter && typeof filter.status === "string") {
26794
+ count = inputs.countsByTypeAndStatus?.[check.entity_type]?.[filter.status] ?? 0;
26676
26795
  } else {
26677
26796
  count = inputs.countsByType[check.entity_type] ?? 0;
26678
26797
  }
@@ -26739,11 +26858,12 @@ function evaluateDomainPopulation(check, inputs) {
26739
26858
  function evaluateOrphanCount(check, inputs) {
26740
26859
  return compareNumber(inputs.orphanCount, check.comparison, check.threshold);
26741
26860
  }
26742
- function buildViolation(ap) {
26861
+ function buildViolation(ap, concern) {
26743
26862
  return {
26744
26863
  anti_pattern_id: ap.id,
26745
26864
  name: ap.name,
26746
26865
  severity: ap.severity,
26866
+ concern,
26747
26867
  target_entities: collectTargetEntities(ap.structured_condition),
26748
26868
  description: ap.description,
26749
26869
  why_it_matters: ap.why_it_matters,
@@ -29571,7 +29691,7 @@ function serializeSingleWithHeader(doc, opts) {
29571
29691
  const summary = deriveSummary(product.description);
29572
29692
  if (summary) header.summary = summary;
29573
29693
  header.counts = { nodes: doc.nodes?.length ?? 0, edges: doc.edges?.length ?? 0 };
29574
- if (doc.member_kind === "org_rollup" || doc.member_kind === "watched") {
29694
+ if (doc.member_kind === "org_rollup" || doc.member_kind === "watched" || doc.member_kind === "operating_function") {
29575
29695
  header.member_kind = doc.member_kind;
29576
29696
  }
29577
29697
  header.provenance = buildProvenance(doc, opts);
@@ -29597,10 +29717,12 @@ function serializePortfolioWithHeader(doc, opts) {
29597
29717
  const memberKindOf = (p) => p.member_kind ?? "product";
29598
29718
  const watchedCount = members.filter((p) => memberKindOf(p) === "watched").length;
29599
29719
  const rollupCount = members.filter((p) => memberKindOf(p) === "org_rollup").length;
29720
+ const operatingFunctionCount = members.filter((p) => memberKindOf(p) === "operating_function").length;
29600
29721
  header.counts = {
29601
29722
  products: members.filter((p) => memberKindOf(p) === "product").length,
29602
29723
  ...watchedCount > 0 ? { watched_products: watchedCount } : {},
29603
29724
  ...rollupCount > 0 ? { org_rollups: rollupCount } : {},
29725
+ ...operatingFunctionCount > 0 ? { operating_functions: operatingFunctionCount } : {},
29604
29726
  product_areas: doc.product_areas?.length ?? 0,
29605
29727
  portfolios: doc.portfolios?.length ?? 0,
29606
29728
  cross_edges: doc.cross_edges?.length ?? 0
@@ -29659,7 +29781,9 @@ function normalizeDocument(obj) {
29659
29781
  edges: raw.edges ?? []
29660
29782
  };
29661
29783
  const memberKind = header?.member_kind ?? raw.member_kind;
29662
- if (memberKind === "org_rollup" || memberKind === "watched") out.member_kind = memberKind;
29784
+ if (memberKind === "org_rollup" || memberKind === "watched" || memberKind === "operating_function") {
29785
+ out.member_kind = memberKind;
29786
+ }
29663
29787
  if (_integrity) out._integrity = _integrity;
29664
29788
  return out;
29665
29789
  }
@@ -29672,7 +29796,7 @@ function isCanonical(text) {
29672
29796
  }
29673
29797
 
29674
29798
  // src/index.ts
29675
- var UPG_VERSION = "0.16.1";
29799
+ var UPG_VERSION = "0.17.0";
29676
29800
  var UPG_FORMAT_VERSION = "0.4.0";
29677
29801
  var MARKDOWN_FORMAT_VERSION = "0.1";
29678
29802
  var UPG_TYPES = getTypes();
@@ -29743,6 +29867,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29743
29867
  BUSINESS_MARKETING_AUDIENCE_FIRST_PLAYBOOK,
29744
29868
  CLASSIFICATION_EDGE_PROPERTY_SCHEMA,
29745
29869
  CONTENT_DEPTH_WARNING_RULES,
29870
+ COVERAGE_ANTI_PATTERNS,
29746
29871
  DEFAULT_ENTITY_EMOJI,
29747
29872
  DEFERRED_PROPOSED_BY_CONTRACT,
29748
29873
  DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
@@ -29762,8 +29887,10 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29762
29887
  REFLECT_MODES,
29763
29888
  REGISTRY_PRODUCT_ID,
29764
29889
  STRATEGY_OUTCOMES_PLAYBOOK,
29890
+ THIN_GRAPH_THRESHOLD,
29765
29891
  UPG_ACTIVE_TYPES,
29766
29892
  UPG_ANTI_PATTERNS,
29893
+ UPG_ANTI_PATTERN_CONCERNS,
29767
29894
  UPG_APPROACHES,
29768
29895
  UPG_APPROACHES_BY_ID,
29769
29896
  UPG_AREA_TAXONOMY,
@@ -29824,6 +29951,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29824
29951
  UPG_TYPE_LABELS,
29825
29952
  UPG_TYPE_LABELS_MAP,
29826
29953
  UPG_TYPE_NAMES,
29954
+ UPG_VALIDATION_PROFILES,
29827
29955
  UPG_VALID_CHILDREN,
29828
29956
  UPG_VERSION,
29829
29957
  UPG_WILDCARD_ENDPOINT,
@@ -29836,6 +29964,9 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29836
29964
  collectSlugsForType,
29837
29965
  compareVersions,
29838
29966
  computeBodyChecksum,
29967
+ concernEvaluatedFor,
29968
+ concernFor,
29969
+ concernGatesFor,
29839
29970
  describeTreePattern,
29840
29971
  edgeCarriesProperties,
29841
29972
  evaluateAntiPatterns,
@@ -29910,6 +30041,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29910
30041
  isRegisteredPolymorphicEdge,
29911
30042
  isRuntimeStateShapedProperty,
29912
30043
  isSubSequenceStep,
30044
+ isThinCoverageAdvisory,
29913
30045
  isUPGDocument,
29914
30046
  listPropertiesByModifier,
29915
30047
  listStatusMigrations,
@@ -29935,6 +30067,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
29935
30067
  validateProductStageStrict,
29936
30068
  validateUPGDocument,
29937
30069
  validateUPGFramework,
30070
+ validationProfileFor,
29938
30071
  walkMigrationChainToCanonical
29939
30072
  });
29940
30073
  //# sourceMappingURL=index.cjs.map