@unified-product-graph/core 0.17.1 → 0.17.3

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.js CHANGED
@@ -1029,7 +1029,7 @@ var UPG_EDGE_CATALOG = {
1029
1029
  persona_aspires_to_desired_outcome: { forward_verb: "aspires_to", reverse_verb: "aspirational_for", classification: "hierarchy", source_type: "persona", target_type: "desired_outcome" },
1030
1030
  // Delegation (0.11.6): a persona delegates work to another. The common case is human -> agent
1031
1031
  // (Content Ops Lead -> Content Agent), but it also expresses human -> human (editor -> reviewer).
1032
- persona_delegates_to_persona: { forward_verb: "delegates_to", reverse_verb: "delegated_by", classification: "semantic", source_type: "persona", target_type: "persona" },
1032
+ persona_delegates_to_persona: { forward_verb: "delegates_to", reverse_verb: "delegated_by", classification: "semantic", source_type: "persona", target_type: "persona", cross_product_eligible: true },
1033
1033
  persona_incurs_switching_cost: { forward_verb: "incurs", reverse_verb: "incurred_by", classification: "hierarchy", source_type: "persona", target_type: "switching_cost" },
1034
1034
  job_surfaces_need: { forward_verb: "surfaces", reverse_verb: "surfaces_from", classification: "causal", source_type: "job", target_type: "need" },
1035
1035
  job_motivates_desired_outcome: { forward_verb: "motivates", reverse_verb: "motivated_by", classification: "causal", source_type: "job", target_type: "desired_outcome" },
@@ -1147,15 +1147,15 @@ var UPG_EDGE_CATALOG = {
1147
1147
  // Dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so it also spans our
1148
1148
  // product graph and a watched competitor-intelligence graph. Distinct from
1149
1149
  // `competitor_feature_inspires_feature` (ideation lineage, not parity).
1150
- feature_rivals_competitor_feature: { forward_verb: "rivals", reverse_verb: "is_rivalled_by", classification: "cross-domain", source_type: "feature", target_type: "competitor_feature", carries_properties: true },
1150
+ feature_rivals_competitor_feature: { forward_verb: "rivals", reverse_verb: "is_rivalled_by", classification: "cross-domain", source_type: "feature", target_type: "competitor_feature", carries_properties: true, cross_product_eligible: true },
1151
1151
  // 0.10.0 (spec issue #41): competitor_signal is a dated competitor move (feature
1152
1152
  // launch / pricing change / acquisition / partnership / market entry) emitted by a
1153
1153
  // competitor and mapped onto our portfolio. `emits` is within the watched graph;
1154
1154
  // `maps_to_feature` / `surfaces_opportunity` cross from the watched signal into our
1155
1155
  // product graph (dual-registered as cross-edges in UPG_CROSS_EDGE_TYPES).
1156
1156
  competitor_emits_competitor_signal: { forward_verb: "emits", reverse_verb: "emitted_by", classification: "hierarchy", source_type: "competitor", target_type: "competitor_signal" },
1157
- competitor_signal_maps_to_feature: { forward_verb: "maps_to", reverse_verb: "targeted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "feature" },
1158
- competitor_signal_surfaces_opportunity: { forward_verb: "surfaces", reverse_verb: "prompted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "opportunity" },
1157
+ competitor_signal_maps_to_feature: { forward_verb: "maps_to", reverse_verb: "targeted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "feature", cross_product_eligible: true },
1158
+ competitor_signal_surfaces_opportunity: { forward_verb: "surfaces", reverse_verb: "prompted_by_signal", classification: "cross-domain", source_type: "competitor_signal", target_type: "opportunity", cross_product_eligible: true },
1159
1159
  competitive_analysis_analyses_competitor: { forward_verb: "analyses", reverse_verb: "analysed_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "competitor" },
1160
1160
  competitive_analysis_identifies_market_trend: { forward_verb: "identifies", reverse_verb: "identified_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_trend" },
1161
1161
  competitive_analysis_scopes_market_segment: { forward_verb: "scopes", reverse_verb: "scoped_in", classification: "hierarchy", source_type: "competitive_analysis", target_type: "market_segment" },
@@ -1188,13 +1188,13 @@ var UPG_EDGE_CATALOG = {
1188
1188
  // competitor can be classified directly against a `registry/{classification_value}`
1189
1189
  // canonical, eliminating the per-graph taxonomy node. Within-graph (the catalogue
1190
1190
  // case) here; cross-product against the registry canonical via create_cross_product_edge.
1191
- 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 },
1191
+ 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, cross_product_eligible: true },
1192
1192
  // 0.10.3: the polymorphic, type-agnostic sibling. ANY node (a feature, a
1193
1193
  // product, a market_segment) classified against a classification_value, so
1194
1194
  // classification is not welded to the competitor type. Also dual-registered as
1195
1195
  // a cross-edge for the registry-canonical case. source_type:'node' (wildcard);
1196
1196
  // listed in UPG_POLYMORPHIC_EDGE_KEYS.
1197
- 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 },
1197
+ 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, cross_product_eligible: true },
1198
1198
  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" },
1199
1199
  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" },
1200
1200
  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" },
@@ -1247,28 +1247,28 @@ var UPG_EDGE_CATALOG = {
1247
1247
  feedback_program_has_beta_program: { forward_verb: "has", reverse_verb: "part_of", classification: "hierarchy", source_type: "feedback_program", target_type: "beta_program" },
1248
1248
  // ── Part 2: Strategic & Execution Spine (Ring 2) ───────────────────────────
1249
1249
  // 2.1 Strategic Domain
1250
- product_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "hierarchy", source_type: "product", target_type: "outcome" },
1251
- product_targets_objective: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "hierarchy", source_type: "product", target_type: "objective" },
1252
- product_guided_by_vision: { forward_verb: "guided_by", reverse_verb: "guides", classification: "hierarchy", source_type: "product", target_type: "vision" },
1250
+ product_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "hierarchy", source_type: "product", target_type: "outcome", cross_product_eligible: true },
1251
+ product_targets_objective: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "hierarchy", source_type: "product", target_type: "objective", cross_product_eligible: true },
1252
+ product_guided_by_vision: { forward_verb: "guided_by", reverse_verb: "guides", classification: "hierarchy", source_type: "product", target_type: "vision", cross_product_eligible: true },
1253
1253
  // product→{mission,strategic_theme,strategic_pillar,initiative,capability,value_stream,assumption}
1254
1254
  // are semantic "how the product relates to the strategic cascade", not containment.
1255
1255
  // The containment chain is product → vision → mission → strategic_pillar → … so these
1256
1256
  // targets are reachable via the vision/mission hierarchy already.
1257
- product_fulfils_mission: { forward_verb: "fulfils", reverse_verb: "fulfilled_by", classification: "semantic", source_type: "product", target_type: "mission" },
1258
- product_organises_around_strategic_theme: { forward_verb: "organises_around", reverse_verb: "organises", classification: "semantic", source_type: "product", target_type: "strategic_theme" },
1259
- product_stands_on_strategic_pillar: { forward_verb: "stands_on", reverse_verb: "supports", classification: "semantic", source_type: "product", target_type: "strategic_pillar" },
1260
- product_invests_in_initiative: { forward_verb: "invests_in", reverse_verb: "investment_of", classification: "semantic", source_type: "product", target_type: "initiative" },
1257
+ product_fulfils_mission: { forward_verb: "fulfils", reverse_verb: "fulfilled_by", classification: "semantic", source_type: "product", target_type: "mission", cross_product_eligible: true },
1258
+ product_organises_around_strategic_theme: { forward_verb: "organises_around", reverse_verb: "organises", classification: "semantic", source_type: "product", target_type: "strategic_theme", cross_product_eligible: true },
1259
+ product_stands_on_strategic_pillar: { forward_verb: "stands_on", reverse_verb: "supports", classification: "semantic", source_type: "product", target_type: "strategic_pillar", cross_product_eligible: true },
1260
+ product_invests_in_initiative: { forward_verb: "invests_in", reverse_verb: "investment_of", classification: "semantic", source_type: "product", target_type: "initiative", cross_product_eligible: true },
1261
1261
  product_develops_capability: { forward_verb: "develops", reverse_verb: "developed_by", classification: "semantic", source_type: "product", target_type: "capability" },
1262
1262
  product_delivers_through_value_stream: { forward_verb: "delivers_through", reverse_verb: "delivers_for", classification: "semantic", source_type: "product", target_type: "value_stream" },
1263
1263
  product_holds_assumption: { forward_verb: "holds", reverse_verb: "held_by", classification: "semantic", source_type: "product", target_type: "assumption" },
1264
- product_measures_with_metric: { forward_verb: "measures_with", reverse_verb: "measures", classification: "hierarchy", source_type: "product", target_type: "metric" },
1265
- outcome_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "outcome", target_type: "metric" },
1264
+ product_measures_with_metric: { forward_verb: "measures_with", reverse_verb: "measures", classification: "hierarchy", source_type: "product", target_type: "metric", cross_product_eligible: true },
1265
+ outcome_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "outcome", target_type: "metric", cross_product_eligible: true },
1266
1266
  // (UPG-677) outcome_tracked_by_metric retired — near-synonym of
1267
1267
  // outcome_measured_by_metric (the kept canonical key). Captain's lean =
1268
1268
  // collapse. See UPG_EDGE_MIGRATIONS['0.9.9'].
1269
- objective_achieved_through_key_result: { forward_verb: "achieved_through", reverse_verb: "achieves", classification: "hierarchy", source_type: "objective", target_type: "key_result" },
1270
- objective_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "objective", target_type: "metric" },
1271
- key_result_quantified_by_metric: { forward_verb: "quantified_by", reverse_verb: "quantifies", classification: "hierarchy", source_type: "key_result", target_type: "metric" },
1269
+ objective_achieved_through_key_result: { forward_verb: "achieved_through", reverse_verb: "achieves", classification: "hierarchy", source_type: "objective", target_type: "key_result", cross_product_eligible: true },
1270
+ objective_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "objective", target_type: "metric", cross_product_eligible: true },
1271
+ key_result_quantified_by_metric: { forward_verb: "quantified_by", reverse_verb: "quantifies", classification: "hierarchy", source_type: "key_result", target_type: "metric", cross_product_eligible: true },
1272
1272
  // (UPG-677) key_result_tracked_by_metric retired — near-synonym of
1273
1273
  // key_result_quantified_by_metric (the kept canonical key). Captain's lean =
1274
1274
  // collapse. See UPG_EDGE_MIGRATIONS['0.9.9'].
@@ -1278,7 +1278,7 @@ var UPG_EDGE_CATALOG = {
1278
1278
  strategic_pillar_enables_capability: { forward_verb: "enables", reverse_verb: "enabled_by", classification: "hierarchy", source_type: "strategic_pillar", target_type: "capability" },
1279
1279
  strategic_pillar_delivers_value_stream: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "hierarchy", source_type: "strategic_pillar", target_type: "value_stream" },
1280
1280
  strategic_pillar_decided_via_decision: { forward_verb: "decided_via", reverse_verb: "decided_for", classification: "hierarchy", source_type: "strategic_pillar", target_type: "decision" },
1281
- strategic_theme_pursues_initiative: { forward_verb: "pursues", reverse_verb: "pursued_under", classification: "hierarchy", source_type: "strategic_theme", target_type: "initiative" },
1281
+ strategic_theme_pursues_initiative: { forward_verb: "pursues", reverse_verb: "pursued_under", classification: "hierarchy", source_type: "strategic_theme", target_type: "initiative", cross_product_eligible: true },
1282
1282
  // v0.9.0 (UPG-660): the soft bridge from the annual strategy focus area to the
1283
1283
  // roadmap grouping that realises it. Semantic, NOT hierarchy: strategic_theme and
1284
1284
  // roadmap_theme sit on different spines (strategy cascade vs roadmap cascade), so
@@ -1306,11 +1306,11 @@ var UPG_EDGE_CATALOG = {
1306
1306
  // surfaces the upward-rollup read. Mirrors `strategic_pillar_organises_
1307
1307
  // strategic_theme` (pillar → theme), completing the strategic cascade:
1308
1308
  // strategic_pillar → strategic_theme → objective → key_result.
1309
- strategic_theme_delivers_outcome: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "causal", source_type: "strategic_theme", target_type: "outcome" },
1310
- strategic_theme_measured_by_key_result: { forward_verb: "measured_by", reverse_verb: "measures", classification: "causal", source_type: "strategic_theme", target_type: "key_result" },
1311
- strategic_theme_contains_objective: { forward_verb: "contains", reverse_verb: "rolls_up_to", classification: "hierarchy", source_type: "strategic_theme", target_type: "objective" },
1309
+ strategic_theme_delivers_outcome: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "causal", source_type: "strategic_theme", target_type: "outcome", cross_product_eligible: true },
1310
+ strategic_theme_measured_by_key_result: { forward_verb: "measured_by", reverse_verb: "measures", classification: "causal", source_type: "strategic_theme", target_type: "key_result", cross_product_eligible: true },
1311
+ strategic_theme_contains_objective: { forward_verb: "contains", reverse_verb: "rolls_up_to", classification: "hierarchy", source_type: "strategic_theme", target_type: "objective", cross_product_eligible: true },
1312
1312
  initiative_assumes_assumption: { forward_verb: "assumes", reverse_verb: "assumed_by", classification: "hierarchy", source_type: "initiative", target_type: "assumption" },
1313
- initiative_drives_outcome: { forward_verb: "drives", reverse_verb: "driven_by", classification: "cross-domain", source_type: "initiative", target_type: "outcome" },
1313
+ initiative_drives_outcome: { forward_verb: "drives", reverse_verb: "driven_by", classification: "cross-domain", source_type: "initiative", target_type: "outcome", cross_product_eligible: true },
1314
1314
  capability_enables_value_stream: { forward_verb: "enables", reverse_verb: "enabled_by", classification: "cross-domain", source_type: "capability", target_type: "value_stream" },
1315
1315
  // v0.5.2 (UPG-528): three capability-anchored edges that complete the
1316
1316
  // Wardley-style value chain: need → capability → capability → feature.
@@ -1448,7 +1448,7 @@ var UPG_EDGE_CATALOG = {
1448
1448
  // brand_identity. Dual-registered: also a cross-edge for the common case where the
1449
1449
  // brand is a registry singleton in another graph (the ratified pattern: brand is a
1450
1450
  // singleton expressed, not instance_of-d); this catalog entry is the within-graph case.
1451
- product_expresses_brand_identity: { forward_verb: "expresses", reverse_verb: "expressed_by", classification: "semantic", source_type: "product", target_type: "brand_identity" },
1451
+ product_expresses_brand_identity: { forward_verb: "expresses", reverse_verb: "expressed_by", classification: "semantic", source_type: "product", target_type: "brand_identity", cross_product_eligible: true },
1452
1452
  design_system_encompasses_user_journey: { forward_verb: "encompasses", reverse_verb: "encompassed_in", classification: "hierarchy", source_type: "design_system", target_type: "user_journey" },
1453
1453
  design_system_encompasses_user_flow: { forward_verb: "encompasses", reverse_verb: "encompassed_in", classification: "hierarchy", source_type: "design_system", target_type: "user_flow" },
1454
1454
  design_system_informed_by_insight: { forward_verb: "informed_by", reverse_verb: "informs", classification: "hierarchy", source_type: "design_system", target_type: "insight" },
@@ -1460,12 +1460,12 @@ var UPG_EDGE_CATALOG = {
1460
1460
  design_component_specified_by_interaction_spec: { forward_verb: "specified_by", reverse_verb: "specifies", classification: "hierarchy", source_type: "design_component", target_type: "interaction_spec" },
1461
1461
  design_component_composes_design_component: { forward_verb: "composes", reverse_verb: "composed_in", classification: "hierarchy", source_type: "design_component", target_type: "design_component" },
1462
1462
  prototype_annotated_with_annotation: { forward_verb: "annotated_with", reverse_verb: "annotates", classification: "hierarchy", source_type: "prototype", target_type: "annotation" },
1463
- screen_renders_design_component: { forward_verb: "renders", reverse_verb: "rendered_on", classification: "hierarchy", source_type: "screen", target_type: "design_component" },
1463
+ screen_renders_design_component: { forward_verb: "renders", reverse_verb: "rendered_on", classification: "hierarchy", source_type: "screen", target_type: "design_component", cross_product_eligible: true },
1464
1464
  // Marketing surface to product (0.12.7, UPG-696/698). A marketing/landing screen
1465
1465
  // markets a product. Dual-registered: also a cross-edge (UPG_CROSS_EDGE_TYPES) for
1466
1466
  // the common case where the product lives in another graph; this catalog entry is
1467
1467
  // the within-graph degenerate case (a product's own landing page).
1468
- screen_markets_product: { forward_verb: "markets", reverse_verb: "marketed_by", classification: "semantic", source_type: "screen", target_type: "product" },
1468
+ screen_markets_product: { forward_verb: "markets", reverse_verb: "marketed_by", classification: "semantic", source_type: "screen", target_type: "product", cross_product_eligible: true },
1469
1469
  // Connective cross-product references (0.13.1, Data's connective-layer brief).
1470
1470
  // Dual-registered (also in UPG_CROSS_EDGE_TYPES) for the common cross-graph case;
1471
1471
  // these catalog entries are the within-graph degenerate case.
@@ -1481,10 +1481,10 @@ var UPG_EDGE_CATALOG = {
1481
1481
  // system (the adoption semantic, mirroring product_implements_specification). Distinct
1482
1482
  // from the hierarchy `product_systematised_in_design_system` (structural containment):
1483
1483
  // same pattern as brand (branded_as = hierarchy, expresses = semantic).
1484
- screen_targets_competitor: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "semantic", source_type: "screen", target_type: "competitor" },
1485
- feature_surfaces_product: { forward_verb: "surfaces", reverse_verb: "surfaced_by", classification: "semantic", source_type: "feature", target_type: "product" },
1486
- feature_uses_design_component: { forward_verb: "uses", reverse_verb: "used_by", classification: "semantic", source_type: "feature", target_type: "design_component" },
1487
- product_implements_design_system: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "semantic", source_type: "product", target_type: "design_system" },
1484
+ screen_targets_competitor: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "semantic", source_type: "screen", target_type: "competitor", cross_product_eligible: true },
1485
+ feature_surfaces_product: { forward_verb: "surfaces", reverse_verb: "surfaced_by", classification: "semantic", source_type: "feature", target_type: "product", cross_product_eligible: true },
1486
+ feature_uses_design_component: { forward_verb: "uses", reverse_verb: "used_by", classification: "semantic", source_type: "feature", target_type: "design_component", cross_product_eligible: true },
1487
+ product_implements_design_system: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "semantic", source_type: "product", target_type: "design_system", cross_product_eligible: true },
1488
1488
  screen_navigates_to_screen: { forward_verb: "navigates_to", reverse_verb: "navigated_from", classification: "hierarchy", source_type: "screen", target_type: "screen" },
1489
1489
  screen_surfaces_feature: { forward_verb: "surfaces", reverse_verb: "surfaced_on", classification: "cross-domain", source_type: "screen", target_type: "feature" },
1490
1490
  screen_wireframed_as_wireframe: { forward_verb: "wireframed_as", reverse_verb: "wireframes", classification: "hierarchy", source_type: "screen", target_type: "wireframe" },
@@ -2068,6 +2068,16 @@ var UPG_EDGE_CATALOG = {
2068
2068
  // UPG_EDGE_MIGRATIONS['0.9.9'].
2069
2069
  product_organised_into_department: { forward_verb: "organised_into", reverse_verb: "organises", classification: "hierarchy", source_type: "product", target_type: "department" },
2070
2070
  department_contains_team: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "department", target_type: "team" },
2071
+ // Second-level team nesting (0.17.2). department_contains_team covers the first
2072
+ // level (department -> team); this covers a team nested inside another team in
2073
+ // the same department (a sub-team or squad under a parent team). Modelled
2074
+ // parent -> child like feature_area_contains_feature_area, so the org map builds
2075
+ // correctly in get_tree (which reads source as the parent); the reverse verb
2076
+ // belongs_to gives the upward "this team is part of that team" read. The
2077
+ // same-department expectation is advisory (a write-time warning), never a hard
2078
+ // block; cross-department reporting lines belong to person_reports_to_person at
2079
+ // the individual level.
2080
+ team_contains_team: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "team", target_type: "team" },
2071
2081
  department_includes_stakeholder: { forward_verb: "includes", reverse_verb: "included_in", classification: "hierarchy", source_type: "department", target_type: "stakeholder" },
2072
2082
  // NOTE: `person` is a containment-free entity type (see
2073
2083
  // UPG_CONTAINMENT_FREE_TYPES in grammar/hierarchy.ts). It is referenced
@@ -2326,10 +2336,10 @@ var UPG_EDGE_CATALOG = {
2326
2336
  experiment_run_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "experiment_run", target_type: "metric" },
2327
2337
  solution_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "hierarchy", source_type: "solution", target_type: "metric" },
2328
2338
  // ── Cross-domain: Ownership ──────────────────────────────────────────────────
2329
- node_owned_by_team: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "team" },
2339
+ node_owned_by_team: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "team", cross_product_eligible: true },
2330
2340
  node_owned_by_role: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "role" },
2331
2341
  node_owned_by_stakeholder: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "stakeholder" },
2332
- node_owned_by_department: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "department" },
2342
+ node_owned_by_department: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "department", cross_product_eligible: true },
2333
2343
  node_owned_by_person: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "person" },
2334
2344
  // ── Cross-domain: Architecture (DDD) ─────────────────────────────────────────
2335
2345
  // Service, domain_event, domain_entity, aggregate, read_model, api_contract,
@@ -2993,7 +3003,7 @@ var UPG_EDGE_CATALOG = {
2993
3003
  // not the outcome it exists to move; the strategic spine was severed at its
2994
3004
  // anchor (0 outbound to outcome). An objective ADVANCES an outcome. Intra-
2995
3005
  // domain (both in strategy) and directional → causal.
2996
- objective_advances_outcome: { forward_verb: "advances", reverse_verb: "advanced_by", classification: "causal", source_type: "objective", target_type: "outcome" },
3006
+ objective_advances_outcome: { forward_verb: "advances", reverse_verb: "advanced_by", classification: "causal", source_type: "objective", target_type: "outcome", cross_product_eligible: true },
2997
3007
  // ── F6: Priority 2 — scored / synthesis / verdict dead-end leaves (P-A) ─────
2998
3008
  // desired_outcome (Ulwick-scored: importance × satisfaction) had 0 outbound —
2999
3009
  // the scores could not flow anywhere. An under-served desired_outcome REVEALS
@@ -3076,9 +3086,16 @@ var UPG_EDGE_CATALOG = {
3076
3086
  specification_governed_by_organization: { forward_verb: "governed_by", reverse_verb: "governs", classification: "semantic", source_type: "specification", target_type: "organization" },
3077
3087
  // Operating-lifecycle cross-edges (0.11.6): a product's journey phase realises a canonical
3078
3088
  // operating stage (the cross-surface join key); a stage is measured by a rollup metric (loop close).
3079
- journey_phase_realises_operating_stage: { forward_verb: "realises", reverse_verb: "realised_by", classification: "cross-domain", source_type: "journey_phase", target_type: "operating_stage" },
3089
+ journey_phase_realises_operating_stage: { forward_verb: "realises", reverse_verb: "realised_by", classification: "cross-domain", source_type: "journey_phase", target_type: "operating_stage", cross_product_eligible: true },
3080
3090
  operating_stage_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "cross-domain", source_type: "operating_stage", target_type: "metric" }
3081
3091
  };
3092
+ var UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES = Object.keys(UPG_EDGE_CATALOG).filter(
3093
+ (k) => UPG_EDGE_CATALOG[k].cross_product_eligible === true
3094
+ );
3095
+ function isCrossProductEligible(type) {
3096
+ const def = UPG_EDGE_CATALOG[type];
3097
+ return def?.cross_product_eligible === true;
3098
+ }
3082
3099
  var UPG_POLYMORPHIC_EDGE_KEYS = [
3083
3100
  // Universal semantic verbs
3084
3101
  "node_informs_node",
@@ -3138,46 +3155,47 @@ function isLegacyProductStage(stage) {
3138
3155
  }
3139
3156
 
3140
3157
  // src/shapes/document.ts
3141
- var UPG_CROSS_EDGE_TYPES = [
3158
+ var UPG_CROSS_ONLY_EDGE_TYPES = [
3159
+ // Peer overlap: "these two products share / overlap on this thing." Symmetric,
3160
+ // no canonical instance_of identity. shares_job / shares_need are the job/need
3161
+ // siblings of the persona/competitor/metric peers (connective layer, 0.13.1).
3142
3162
  "shares_persona",
3143
3163
  "shares_competitor",
3144
3164
  "shares_metric",
3165
+ "shares_job",
3166
+ "shares_need",
3167
+ // Product-to-product relationships.
3145
3168
  "depends_on_product",
3169
+ // runtime dependency (built-on / dogfood), not containment
3146
3170
  "cannibalises",
3147
3171
  "succeeds",
3148
3172
  "hosts",
3173
+ // host runs the hosted product inside itself (container -> contained)
3174
+ // OKR / measurement rollup primitives: the cascade ACROSS products (subordinate
3175
+ // -> superior). `contributes_to` rolls a product strategy entity up to a higher-
3176
+ // level one; `rolls_up_to` feeds a product metric into a higher-level north-star.
3149
3177
  "contributes_to",
3178
+ "rolls_up_to",
3179
+ // Canonical instance + area-to-audience: the target lives in the portfolio
3180
+ // `registry` section (`registry/{node_id}`). `instance_of` is same-type
3181
+ // canonical-to-instance; the area edges are minted via `link_area_to_audience`.
3150
3182
  "instance_of",
3151
3183
  "area_serves_persona",
3152
3184
  "area_targets_market_segment",
3153
- "rolls_up_to",
3185
+ // Foundations (0.9.12): a product-graph entity links to a canonical specification
3186
+ // or primitive in the registry (`registry/{node_id}` target, same shape as
3187
+ // instance_of). Registry-internal spec-to-spec links are catalog edges, not these.
3154
3188
  "product_implements_specification",
3155
3189
  "product_exposes_specification",
3156
3190
  "feature_conforms_to_specification",
3157
3191
  "api_contract_speaks_specification",
3158
3192
  "product_exposes_primitive",
3159
3193
  "feature_manipulates_primitive",
3160
- "primitive_stored_as_data_type",
3161
- "feature_rivals_competitor_feature",
3162
- "competitor_signal_maps_to_feature",
3163
- "competitor_signal_surfaces_opportunity",
3164
- "competitor_classified_as_classification_value",
3165
- "node_classified_as_classification_value",
3166
- "journey_phase_realises_operating_stage",
3167
- "screen_markets_product",
3168
- "screen_renders_design_component",
3169
- "product_expresses_brand_identity",
3170
- // Connective cross-product layer (0.13.1) — see UPGCrossEdgeType above.
3171
- "shares_job",
3172
- "shares_need",
3173
- "persona_delegates_to_persona",
3174
- "screen_targets_competitor",
3175
- "feature_surfaces_product",
3176
- "feature_uses_design_component",
3177
- "product_implements_design_system",
3178
- // Org-link (0.17.0): dual-registered ownership edges (see UPGCrossEdgeType above).
3179
- "node_owned_by_team",
3180
- "node_owned_by_department"
3194
+ "primitive_stored_as_data_type"
3195
+ ];
3196
+ var UPG_CROSS_EDGE_TYPES = [
3197
+ ...UPG_CROSS_ONLY_EDGE_TYPES,
3198
+ ...UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES
3181
3199
  ];
3182
3200
  var REGISTRY_PRODUCT_ID = "registry";
3183
3201
  var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
@@ -3631,7 +3649,11 @@ var UPG_VALID_CHILDREN = {
3631
3649
  "skill",
3632
3650
  "ceremony",
3633
3651
  "capacity_plan",
3634
- "decision"
3652
+ "decision",
3653
+ // Self-nesting (0.17.2): a parent team contains its sub-teams / squads, one
3654
+ // level below department_contains_team. Backs team_contains_team. Mirrors
3655
+ // feature_area_contains_feature_area (same-type child listed last).
3656
+ "team"
3635
3657
  ],
3636
3658
  // ── Data & Analytics hierarchy ───────────────────────────────────────────────
3637
3659
  data_source: ["metric", "data_pipeline", "data_lineage", "event_schema"],
@@ -10624,6 +10646,7 @@ var UPG_PROPERTY_SCHEMA = {
10624
10646
  // ConstraintProperties: Constraint: a named limitation or boundary on product creation.
10625
10647
  constraint: {
10626
10648
  constraint_kind: { type: "string", enum: ["resource", "technical", "regulatory", "temporal", "compliance", "other"], description: "Limitation category" },
10649
+ constraint_origin: { type: "string", enum: ["internal", "external"], description: "Provenance: internal (a self-imposed principle or operating tenet) or external (a limit, requirement, or ceiling imposed from outside)." },
10627
10650
  constraint_status: { type: "string", enum: ["binding", "advisory", "lifted"], description: "Whether binding, advisory, or lifted" },
10628
10651
  rule_strength: { type: "string", enum: ["must", "must_not", "exception", "warning", "guideline"], description: "Enforcement strictness. Reuses the governance/guideline rule vocabulary." },
10629
10652
  source: { type: "string", description: "Free-text origin: policy document, regulation, stakeholder, technical doc." },
@@ -21014,6 +21037,30 @@ var UPG_TREE_PATTERNS = [
21014
21037
  metric: [opt("metric"), opt("outcome")]
21015
21038
  },
21016
21039
  natural_depth: 3
21040
+ },
21041
+ {
21042
+ id: "org",
21043
+ label: "Org chart",
21044
+ description: "Departments and the teams they contain, plus the sub-teams nested within those teams. The people-structure org chart, walked by containment (department_contains_team, then team_contains_team). Distinct from product_area, which is the product classification axis, not the org.",
21045
+ region: "operations_quality",
21046
+ gap_policy: "all-optional",
21047
+ anchor_type: "department",
21048
+ // A graph with no department roots at the team (team -> sub-team), reported
21049
+ // anchor_resolved_from: department. A sub-team also renders as a bare team
21050
+ // root when its parent department is not modelled.
21051
+ fallback_anchors: ["team"],
21052
+ child_map: {
21053
+ // department -> team (department_contains_team) is the first level; team ->
21054
+ // team (team_contains_team, 0.17.2) is the second, so a sub-team nested one
21055
+ // level deeper than department_contains_team renders under its parent team.
21056
+ // Self-nesting: the assembler's `seen` set terminates a team -> team cycle
21057
+ // for free (a team met again becomes a shared reference).
21058
+ department: [opt("team")],
21059
+ team: [opt("team")]
21060
+ },
21061
+ // Default to 3 tiers (department -> team -> sub-team); `depth` extends into
21062
+ // deeper team_contains_team nesting.
21063
+ natural_depth: 3
21017
21064
  }
21018
21065
  ];
21019
21066
  var UPG_TREE_PATTERNS_BY_ID = Object.fromEntries(
@@ -22803,6 +22850,12 @@ var STRATEGY_GUIDE = {
22803
22850
  description: "Vision grounds mission, product pursues outcomes and targets objectives, objectives achieved through key results measured by metrics",
22804
22851
  entity_types: ["vision", "mission", "outcome", "objective", "key_result", "metric"],
22805
22852
  edge_chain: ["product_guided_by_vision", "product_fulfils_mission", "vision_realised_through_mission", "product_pursues_outcome", "product_targets_objective", "objective_achieved_through_key_result", "outcome_measured_by_metric"]
22853
+ },
22854
+ {
22855
+ name: "Rendering the company spine",
22856
+ description: "The company OKR / strategy / north-star spine already renders with get_tree, no new pattern needed: switch to the org_rollup member (the company rollup graph) and run the okr, strategy, or north_star tree pattern there. In a portfolio the spine can span files, a rollup theme laddering into product-graph objectives through the cross-product strategic_theme_contains_objective edge; get_tree walks the active member, so render the rollup for the whole-company view and a product for its slice.",
22857
+ entity_types: ["strategic_theme", "objective", "key_result", "metric"],
22858
+ edge_chain: ["strategic_theme_contains_objective", "objective_achieved_through_key_result", "key_result_quantified_by_metric"]
22806
22859
  }
22807
22860
  ],
22808
22861
  required_bridges: [
@@ -22813,7 +22866,8 @@ var STRATEGY_GUIDE = {
22813
22866
  anti_patterns: [
22814
22867
  { description: "Outcomes without metrics. Measurement is the signal an outcome happened." },
22815
22868
  { description: "Objectives without key results: an objective without measurement is a wish" },
22816
- { description: "Too many strategic themes: focus beats breadth, aim for 2-4 active themes" }
22869
+ { description: "Too many strategic themes: focus beats breadth, aim for 2-4 active themes" },
22870
+ { description: "Constraints without an origin: a qualitative guardrail lives on a constraint, so set constraint_origin to say where it comes from. internal marks a self-imposed principle or operating tenet the team commits to; external marks a limit, requirement, or ceiling imposed from outside (a regulation, a budget cap, a platform bound)." }
22817
22871
  ]
22818
22872
  };
22819
22873
  var PRODUCT_SPEC_GUIDE = {
@@ -23364,6 +23418,12 @@ var TEAM_ORG_GUIDE = {
23364
23418
  description: "Teams are staffed with roles, target OKRs, reflect in retrospectives, and are blocked by dependencies",
23365
23419
  entity_types: ["team", "role", "team_okr", "retrospective", "dependency"],
23366
23420
  edge_chain: ["team_staffed_with_role", "team_targets_team_okr", "team_reflects_in_retrospective", "dependency_blocks_team"]
23421
+ },
23422
+ {
23423
+ name: "Org Nesting",
23424
+ description: "A department contains its teams; a parent team contains its sub-teams or squads one level deeper. Keep a sub-team in the same department as its parent team. Cross-department reporting lines are modelled at the individual level with person_reports_to_person, not by nesting a team under a parent in another department.",
23425
+ entity_types: ["department", "team"],
23426
+ edge_chain: ["department_contains_team", "team_contains_team"]
23367
23427
  }
23368
23428
  ],
23369
23429
  required_bridges: [
@@ -23374,6 +23434,7 @@ var TEAM_ORG_GUIDE = {
23374
23434
  { description: "Teams without OKRs: every team needs clear goals" },
23375
23435
  { description: "Dependencies without both teams linked: a dependency must connect blocker and blocked" },
23376
23436
  { description: "Retrospectives without action items: reflection without action is just venting" },
23437
+ { description: "Nesting a sub-team under a parent team in a different department: a team_contains_team nesting should stay within one department (both teams sharing a department_contains_team parent). A cross-department nesting is almost always a modelling error; model a cross-department reporting line at the individual level with person_reports_to_person instead." },
23377
23438
  { description: "Double-modelling the org as product_area and team_org: a product_area is the classification axis ON products (which area a product belongs to, the axis used to group products); a team_org department/team is the people structure (the org chart, who reports to whom). Use product_area to classify products and department/team for the org chart, and relate them through the team that owns an area rather than restating one as the other." }
23378
23439
  ]
23379
23440
  };
@@ -26096,7 +26157,7 @@ var UPG_ANTI_PATTERNS = [
26096
26157
  {
26097
26158
  id: "competitors-missing-past-validation",
26098
26159
  name: "Competitor catalogue empty past validation",
26099
- description: "The graph has zero competitor entities at validation stage or later. Past concept, the absence of named competitors usually means alternatives haven't been thought through, not that none exist.",
26160
+ description: "The graph has zero competitor entities at build stage or later. Past validation, the absence of named competitors usually means alternatives haven't been thought through, not that none exist. A graph still at concept or validation is exempt: cataloguing the field can wait until you commit to building.",
26100
26161
  structured_condition: {
26101
26162
  check: {
26102
26163
  type: "benchmark",
@@ -26106,14 +26167,18 @@ var UPG_ANTI_PATTERNS = [
26106
26167
  },
26107
26168
  why_it_matters: "Without competitors in the graph, positioning, differentiation, and switching-cost analysis lack referents. The team is reasoning in a vacuum.",
26108
26169
  remediation: "Catalogue the 3\u20135 closest alternatives users would pick today. Use `/upg-compete` to author.",
26109
- stages: ["validation", "build", "beta", "launch", "growth", "mature"],
26170
+ // Fires from build onward, matching the name (past the validation stage). A
26171
+ // concept or validation graph is still framing the problem; holding it to a
26172
+ // competitor benchmark there is the false alarm this anti-pattern caused on
26173
+ // legitimately-young graphs.
26174
+ stages: ["build", "beta", "launch", "growth", "mature"],
26110
26175
  severity: "medium",
26111
26176
  source: { kind: "industry_practice", category: "product_strategy" }
26112
26177
  },
26113
26178
  {
26114
26179
  id: "persona-count-below-stage-benchmark",
26115
26180
  name: "Persona count below stage benchmark",
26116
- description: "The graph has fewer personas than the stage-appropriate benchmark expects. Persona under-coverage signals the team has not segmented its audience.",
26181
+ description: "The graph has fewer personas than the stage-appropriate benchmark expects, from beta onward. Persona under-coverage at that point signals the team has not segmented its audience. Concept through build are exempt: the sound move there is to start with one beachhead persona and expand as you find fit.",
26117
26182
  structured_condition: {
26118
26183
  check: {
26119
26184
  type: "benchmark",
@@ -26121,9 +26186,13 @@ var UPG_ANTI_PATTERNS = [
26121
26186
  comparison: "below_min"
26122
26187
  }
26123
26188
  },
26124
- why_it_matters: 'A graph with one persona past validation is usually carrying an unexamined "everyone is the same user" assumption.',
26189
+ why_it_matters: "A graph still on a single persona by beta is usually carrying an unexamined assumption that every user is the same.",
26125
26190
  remediation: "Add personas representing the next 1\u20132 most distinct user segments. Use `/upg-new-persona`.",
26126
- stages: ["validation", "build", "beta", "launch", "growth", "mature"],
26191
+ // Fires from beta onward. The persona benchmark expects 2+ from validation,
26192
+ // but the canonical beachhead move is to start with one persona and expand as
26193
+ // fit is found, so holding a build-stage graph to a multi-persona bar is a
26194
+ // false alarm. By beta an unsegmented audience is a real signal.
26195
+ stages: ["beta", "launch", "growth", "mature"],
26127
26196
  severity: "medium",
26128
26197
  source: { kind: "practitioner", attribution: "Alan Cooper, The Inmates Are Running the Asylum" }
26129
26198
  },
@@ -26148,7 +26217,7 @@ var UPG_ANTI_PATTERNS = [
26148
26217
  {
26149
26218
  id: "single-domain-graph",
26150
26219
  name: "Single-domain graph",
26151
- description: "The graph has more than five entities but they all live in a single UPG domain. A real product spans multiple domains; a single-domain graph is usually a deep notebook in one corner with the rest of the picture missing.",
26220
+ description: "The graph has more than five entities but they all live in a single UPG domain, at beta stage or later. A real product spans multiple domains by then; a single-domain graph that far along is usually a deep notebook in one corner with the rest of the picture missing. Concept through build are exempt: a graph legitimately starts deep in one domain and broadens as it matures.",
26152
26221
  structured_condition: {
26153
26222
  operator: "and",
26154
26223
  checks: [
@@ -26158,7 +26227,11 @@ var UPG_ANTI_PATTERNS = [
26158
26227
  },
26159
26228
  why_it_matters: 'Single-domain coverage prevents cross-domain reasoning. The graph cannot answer questions like "which feature serves which persona?" or "which experiment validates which hypothesis?".',
26160
26229
  remediation: "Identify the next adjacent domain (usually `user`, `validation`, or `product_spec`) and add 2\u20133 anchor entities to bridge.",
26161
- stages: ["validation", "build", "beta", "launch"],
26230
+ // Fires from beta onward. Early on a graph is legitimately deep in one domain
26231
+ // (a discovery notebook, a thin internal tool); only once it reaches beta is
26232
+ // single-domain coverage a smell rather than a normal starting shape. Extends
26233
+ // through growth and mature, where a single-domain graph is most telling.
26234
+ stages: ["beta", "launch", "growth", "mature"],
26162
26235
  severity: "medium",
26163
26236
  source: { kind: "fundamental" }
26164
26237
  },
@@ -29556,7 +29629,7 @@ function isCanonical(text) {
29556
29629
  }
29557
29630
 
29558
29631
  // src/index.ts
29559
- var UPG_VERSION = "0.17.1";
29632
+ var UPG_VERSION = "0.17.3";
29560
29633
  var UPG_FORMAT_VERSION = "0.4.0";
29561
29634
  var MARKDOWN_FORMAT_VERSION = "0.1";
29562
29635
  var UPG_TYPES = getTypes();
@@ -29658,6 +29731,8 @@ export {
29658
29731
  UPG_CONTAINMENT_FREE_TYPES,
29659
29732
  UPG_COUNT_BENCHMARKS,
29660
29733
  UPG_CROSS_EDGE_TYPES,
29734
+ UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES,
29735
+ UPG_CROSS_ONLY_EDGE_TYPES,
29661
29736
  UPG_DEPRECATED_TYPES,
29662
29737
  UPG_DOMAINS,
29663
29738
  UPG_DOMAIN_ACTIVATION,
@@ -29788,6 +29863,7 @@ export {
29788
29863
  isCanonical,
29789
29864
  isCanonicalProductStage,
29790
29865
  isContainmentFreeType,
29866
+ isCrossProductEligible,
29791
29867
  isDeprecatedType,
29792
29868
  isDomainGuideStep,
29793
29869
  isEntitySequenceStep,