@unified-product-graph/core 0.8.2 → 0.8.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.js CHANGED
@@ -486,8 +486,8 @@ var UPG_DOMAINS = [
486
486
  {
487
487
  id: "workspace",
488
488
  label: "Workspace",
489
- description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure.",
490
- types: ["workspace"]
489
+ description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure. A framework exercise is a structured workspace: one run of a framework (MoSCoW, RICE, Kano, \u2026) applied to a chosen set of entities, with each entity's result recorded on the exercise-to-entity edge rather than the entity itself.",
490
+ types: ["workspace", "framework_exercise"]
491
491
  }
492
492
  ];
493
493
  var UPG_ENTITY_TO_DOMAIN = Object.freeze(
@@ -913,7 +913,8 @@ var UPG_ENTITY_META = [
913
913
  { name: "integration_partner", type_id: "ent_311", maturity: "stable", since: "0.1.0" },
914
914
  { name: "partner_revenue_share", type_id: "ent_312", maturity: "stable", since: "0.1.0" },
915
915
  // ── Workspace ──
916
- { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" }
916
+ { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
917
+ { name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" }
917
918
  ];
918
919
  var UPG_ENTITY_META_BY_NAME = new Map(
919
920
  UPG_ENTITY_META.map((m) => [m.name, m])
@@ -2066,6 +2067,18 @@ var UPG_EDGE_CATALOG = {
2066
2067
  // universal across the DDD type family. Verbs follow DDD literature:
2067
2068
  // aggregate belongs_to context, context contains aggregates.
2068
2069
  node_belongs_to_bounded_context: { forward_verb: "belongs_to", reverse_verb: "contains", classification: "cross-domain", source_type: "node", target_type: "bounded_context" },
2070
+ // ── Cross-domain: Framework exercises ────────────────────────────────────────
2071
+ // A framework_exercise is one run of a framework (MoSCoW, RICE, Kano, …) over a
2072
+ // set of entities. It `includes` each entity it touches; the framework's
2073
+ // per-entity result — a MoSCoW bucket, a RICE score, a canvas slot, a funnel
2074
+ // stage — rides on this edge's `properties`, NOT on the entity node. A value
2075
+ // that exists only within a specific exercise is a fact about the relationship,
2076
+ // not the entity, so it lives on the edge (same principle as owner-as-edge).
2077
+ // Polymorphic (`target_type: 'node'`): an exercise can include any entity type,
2078
+ // which closes the feature-only limitation structurally. `carries_properties`
2079
+ // opts this edge into the gated edge-property model. See ADR
2080
+ // 2026-06-02-framework-exercises.
2081
+ framework_exercise_includes_node: { forward_verb: "includes", reverse_verb: "included_in", classification: "cross-domain", source_type: "framework_exercise", target_type: "node", carries_properties: true },
2069
2082
  // ── New edges replacing deleted string properties ────────────────────
2070
2083
  // Marketing
2071
2084
  marketing_strategy_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "cross-domain", source_type: "marketing_strategy", target_type: "outcome" },
@@ -2695,7 +2708,9 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
2695
2708
  "node_owned_by_department",
2696
2709
  "node_owned_by_person",
2697
2710
  // Universal architecture references
2698
- "node_belongs_to_bounded_context"
2711
+ "node_belongs_to_bounded_context",
2712
+ // Framework exercises (an exercise can include any entity type)
2713
+ "framework_exercise_includes_node"
2699
2714
  ];
2700
2715
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
2701
2716
  function isPolymorphicEdge(key) {
@@ -2705,6 +2720,10 @@ function isPolymorphicEdge(key) {
2705
2720
  function isRegisteredPolymorphicEdge(key) {
2706
2721
  return _POLY_KEY_SET.has(key);
2707
2722
  }
2723
+ function edgeCarriesProperties(type) {
2724
+ const def = UPG_EDGE_CATALOG[type];
2725
+ return def?.carries_properties === true;
2726
+ }
2708
2727
 
2709
2728
  // src/catalog/legacy-product-stages.ts
2710
2729
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -3356,7 +3375,13 @@ function canBeChildOf(childType, parentType) {
3356
3375
  return getValidChildren(parentType).includes(childType);
3357
3376
  }
3358
3377
  var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
3359
- "person"
3378
+ "person",
3379
+ // A framework_exercise has no structural parent: it anchors to the entities
3380
+ // it scores via the `framework_exercise_includes_node` edge, not by
3381
+ // containment. Modelling it as a product child would force a containment edge
3382
+ // and read against the whole point of the exercise model (relational, not
3383
+ // hierarchical). Same posture as `person`. See ADR 2026-06-02-framework-exercises.
3384
+ "framework_exercise"
3360
3385
  ]);
3361
3386
  function isContainmentFreeType(entityType) {
3362
3387
  return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
@@ -5658,6 +5683,31 @@ var TEMPLATE_LIFECYCLES = [
5658
5683
  // ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
5659
5684
  fromTemplate("deal", SALES_DEAL_TEMPLATE)
5660
5685
  ];
5686
+ var FRAMEWORK_EXERCISE_LIFECYCLE = {
5687
+ entity_type: "framework_exercise",
5688
+ initial_phase: "draft",
5689
+ terminal_phases: ["archived"],
5690
+ phases: [
5691
+ {
5692
+ id: "draft",
5693
+ label: "Draft",
5694
+ description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
5695
+ transitions_to: ["active", "archived"]
5696
+ },
5697
+ {
5698
+ id: "active",
5699
+ label: "Active",
5700
+ description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
5701
+ transitions_to: ["archived"]
5702
+ },
5703
+ {
5704
+ id: "archived",
5705
+ label: "Archived",
5706
+ description: "A past run, retained for provenance. Still queryable but superseded by a newer exercise and hidden from default views. Can be revived to active.",
5707
+ transitions_to: ["active"]
5708
+ }
5709
+ ]
5710
+ };
5661
5711
  var UPG_LIFECYCLES = [
5662
5712
  // Product (root)
5663
5713
  PRODUCT_LIFECYCLE,
@@ -5759,6 +5809,8 @@ var UPG_LIFECYCLES = [
5759
5809
  // Product & Growth
5760
5810
  VARIANT_LIFECYCLE,
5761
5811
  GROWTH_CAMPAIGN_LIFECYCLE,
5812
+ // Workspace
5813
+ FRAMEWORK_EXERCISE_LIFECYCLE,
5762
5814
  // ── Template-generated lifecycles ─────────────────────────────────
5763
5815
  ...TEMPLATE_LIFECYCLES
5764
5816
  ];
@@ -9948,6 +10000,11 @@ var UPG_PROPERTY_SCHEMA = {
9948
10000
  },
9949
10001
  methodology: { type: "string", description: "Forecasting methodology used" }
9950
10002
  },
10003
+ // FrameworkExerciseProperties: Framework exercise: one run of a framework over a set of entities.
10004
+ framework_exercise: {
10005
+ framework_id: { type: "string", description: "Which framework this exercise runs: a framework id (e.g. 'moscow', 'rice-scoring', 'kano-model'). Resolves against the framework catalog." },
10006
+ inputs_snapshot: { type: "object", description: "Optional frozen copy of the framework's input spec at apply time, so a historical exercise still renders correctly if the framework definition later evolves (inputs added, removed, or rescaled)." }
10007
+ },
9951
10008
  // FunnelProperties: Funnel entity.
9952
10009
  funnel: {
9953
10010
  funnel_type: { type: "string", enum: ["acquisition", "activation", "retention", "revenue", "referral", "custom"], description: "Which stage of the customer lifecycle this funnel measures" },
@@ -19566,7 +19623,7 @@ var PORTFOLIO_GUIDE = {
19566
19623
  var WORKSPACE_GUIDE = {
19567
19624
  domain_id: "workspace",
19568
19625
  anchor_entity: "workspace",
19569
- creation_sequence: ["workspace"],
19626
+ creation_sequence: ["workspace", "framework_exercise"],
19570
19627
  patterns: [],
19571
19628
  required_bridges: [],
19572
19629
  anti_patterns: [
@@ -24574,7 +24631,7 @@ var NODE_KEY_ORDER = [
24574
24631
  "sort_order",
24575
24632
  "properties"
24576
24633
  ];
24577
- var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence"];
24634
+ var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "properties"];
24578
24635
  var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence"];
24579
24636
  var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
24580
24637
  function canonicalNode(node) {
@@ -24586,7 +24643,8 @@ function canonicalNode(node) {
24586
24643
  }
24587
24644
  function canonicalEdge(edge) {
24588
24645
  return orderedObject(edge, EDGE_KEY_ORDER, {
24589
- forceKeys: ["id", "source", "target", "type"]
24646
+ forceKeys: ["id", "source", "target", "type"],
24647
+ openKeys: ["properties"]
24590
24648
  });
24591
24649
  }
24592
24650
  function canonicalCrossEdge(edge) {
@@ -24740,7 +24798,7 @@ function isCanonical(text) {
24740
24798
  }
24741
24799
 
24742
24800
  // src/index.ts
24743
- var UPG_VERSION = "0.8.2";
24801
+ var UPG_VERSION = "0.8.4";
24744
24802
  var UPG_FORMAT_VERSION = "0.4.0";
24745
24803
  var MARKDOWN_FORMAT_VERSION = "0.1";
24746
24804
  var UPG_TYPES = getTypes();
@@ -24888,6 +24946,7 @@ export {
24888
24946
  collectSlugsForType,
24889
24947
  compareVersions,
24890
24948
  computeBodyChecksum,
24949
+ edgeCarriesProperties,
24891
24950
  evaluateAntiPatterns,
24892
24951
  findInvalidStatusMigrationTargets,
24893
24952
  formatUpgText,