@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/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.8.4] - 2026-06-02
11
+
12
+ Framework exercises. `UPG_VERSION` is now `0.8.4` (catalogue change). Folds the 0.8.3 patch train (CLI/MCP wiring fixes; no catalogue change).
13
+
14
+ ### Added
15
+ - `framework_exercise` entity type (Workspace domain): one run of a framework over a set of entities. Containment-free, with a draft/active/archived lifecycle and `framework_id` + `inputs_snapshot` properties.
16
+ - `framework_exercise_includes_node` edge (`includes` / `included_in`, polymorphic target): links an exercise to each entity it scores and carries the framework's per-entity result (a MoSCoW bucket, a RICE score, a canvas slot, a funnel stage) as edge properties.
17
+ - Gated edge properties: `UPGEdge.properties` plus a `carries_properties` capability flag on edge definitions. Only edges that opt in may carry a payload; the canonical serializer round-trips it byte-stably. New `edgeCarriesProperties(type)` helper.
18
+
10
19
  ## [0.8.2] - 2026-06-02
11
20
 
12
21
  Co-version with the @unified-product-graph/* 0.8.2 release train.
package/dist/index.cjs CHANGED
@@ -104,6 +104,7 @@ __export(index_exports, {
104
104
  collectSlugsForType: () => collectSlugsForType,
105
105
  compareVersions: () => compareVersions,
106
106
  computeBodyChecksum: () => computeBodyChecksum,
107
+ edgeCarriesProperties: () => edgeCarriesProperties,
107
108
  evaluateAntiPatterns: () => evaluateAntiPatterns,
108
109
  findInvalidStatusMigrationTargets: () => findInvalidStatusMigrationTargets,
109
110
  formatUpgText: () => formatUpgText,
@@ -676,8 +677,8 @@ var UPG_DOMAINS = [
676
677
  {
677
678
  id: "workspace",
678
679
  label: "Workspace",
679
- 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.",
680
- types: ["workspace"]
680
+ 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.",
681
+ types: ["workspace", "framework_exercise"]
681
682
  }
682
683
  ];
683
684
  var UPG_ENTITY_TO_DOMAIN = Object.freeze(
@@ -1103,7 +1104,8 @@ var UPG_ENTITY_META = [
1103
1104
  { name: "integration_partner", type_id: "ent_311", maturity: "stable", since: "0.1.0" },
1104
1105
  { name: "partner_revenue_share", type_id: "ent_312", maturity: "stable", since: "0.1.0" },
1105
1106
  // ── Workspace ──
1106
- { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" }
1107
+ { name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
1108
+ { name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" }
1107
1109
  ];
1108
1110
  var UPG_ENTITY_META_BY_NAME = new Map(
1109
1111
  UPG_ENTITY_META.map((m) => [m.name, m])
@@ -2256,6 +2258,18 @@ var UPG_EDGE_CATALOG = {
2256
2258
  // universal across the DDD type family. Verbs follow DDD literature:
2257
2259
  // aggregate belongs_to context, context contains aggregates.
2258
2260
  node_belongs_to_bounded_context: { forward_verb: "belongs_to", reverse_verb: "contains", classification: "cross-domain", source_type: "node", target_type: "bounded_context" },
2261
+ // ── Cross-domain: Framework exercises ────────────────────────────────────────
2262
+ // A framework_exercise is one run of a framework (MoSCoW, RICE, Kano, …) over a
2263
+ // set of entities. It `includes` each entity it touches; the framework's
2264
+ // per-entity result — a MoSCoW bucket, a RICE score, a canvas slot, a funnel
2265
+ // stage — rides on this edge's `properties`, NOT on the entity node. A value
2266
+ // that exists only within a specific exercise is a fact about the relationship,
2267
+ // not the entity, so it lives on the edge (same principle as owner-as-edge).
2268
+ // Polymorphic (`target_type: 'node'`): an exercise can include any entity type,
2269
+ // which closes the feature-only limitation structurally. `carries_properties`
2270
+ // opts this edge into the gated edge-property model. See ADR
2271
+ // 2026-06-02-framework-exercises.
2272
+ framework_exercise_includes_node: { forward_verb: "includes", reverse_verb: "included_in", classification: "cross-domain", source_type: "framework_exercise", target_type: "node", carries_properties: true },
2259
2273
  // ── New edges replacing deleted string properties ────────────────────
2260
2274
  // Marketing
2261
2275
  marketing_strategy_pursues_outcome: { forward_verb: "pursues", reverse_verb: "pursued_by", classification: "cross-domain", source_type: "marketing_strategy", target_type: "outcome" },
@@ -2885,7 +2899,9 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
2885
2899
  "node_owned_by_department",
2886
2900
  "node_owned_by_person",
2887
2901
  // Universal architecture references
2888
- "node_belongs_to_bounded_context"
2902
+ "node_belongs_to_bounded_context",
2903
+ // Framework exercises (an exercise can include any entity type)
2904
+ "framework_exercise_includes_node"
2889
2905
  ];
2890
2906
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
2891
2907
  function isPolymorphicEdge(key) {
@@ -2895,6 +2911,10 @@ function isPolymorphicEdge(key) {
2895
2911
  function isRegisteredPolymorphicEdge(key) {
2896
2912
  return _POLY_KEY_SET.has(key);
2897
2913
  }
2914
+ function edgeCarriesProperties(type) {
2915
+ const def = UPG_EDGE_CATALOG[type];
2916
+ return def?.carries_properties === true;
2917
+ }
2898
2918
 
2899
2919
  // src/catalog/legacy-product-stages.ts
2900
2920
  var LEGACY_PRODUCT_STAGES = Object.freeze({
@@ -3546,7 +3566,13 @@ function canBeChildOf(childType, parentType) {
3546
3566
  return getValidChildren(parentType).includes(childType);
3547
3567
  }
3548
3568
  var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
3549
- "person"
3569
+ "person",
3570
+ // A framework_exercise has no structural parent: it anchors to the entities
3571
+ // it scores via the `framework_exercise_includes_node` edge, not by
3572
+ // containment. Modelling it as a product child would force a containment edge
3573
+ // and read against the whole point of the exercise model (relational, not
3574
+ // hierarchical). Same posture as `person`. See ADR 2026-06-02-framework-exercises.
3575
+ "framework_exercise"
3550
3576
  ]);
3551
3577
  function isContainmentFreeType(entityType) {
3552
3578
  return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
@@ -5848,6 +5874,31 @@ var TEMPLATE_LIFECYCLES = [
5848
5874
  // ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
5849
5875
  fromTemplate("deal", SALES_DEAL_TEMPLATE)
5850
5876
  ];
5877
+ var FRAMEWORK_EXERCISE_LIFECYCLE = {
5878
+ entity_type: "framework_exercise",
5879
+ initial_phase: "draft",
5880
+ terminal_phases: ["archived"],
5881
+ phases: [
5882
+ {
5883
+ id: "draft",
5884
+ label: "Draft",
5885
+ description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
5886
+ transitions_to: ["active", "archived"]
5887
+ },
5888
+ {
5889
+ id: "active",
5890
+ label: "Active",
5891
+ description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
5892
+ transitions_to: ["archived"]
5893
+ },
5894
+ {
5895
+ id: "archived",
5896
+ label: "Archived",
5897
+ 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.",
5898
+ transitions_to: ["active"]
5899
+ }
5900
+ ]
5901
+ };
5851
5902
  var UPG_LIFECYCLES = [
5852
5903
  // Product (root)
5853
5904
  PRODUCT_LIFECYCLE,
@@ -5949,6 +6000,8 @@ var UPG_LIFECYCLES = [
5949
6000
  // Product & Growth
5950
6001
  VARIANT_LIFECYCLE,
5951
6002
  GROWTH_CAMPAIGN_LIFECYCLE,
6003
+ // Workspace
6004
+ FRAMEWORK_EXERCISE_LIFECYCLE,
5952
6005
  // ── Template-generated lifecycles ─────────────────────────────────
5953
6006
  ...TEMPLATE_LIFECYCLES
5954
6007
  ];
@@ -10138,6 +10191,11 @@ var UPG_PROPERTY_SCHEMA = {
10138
10191
  },
10139
10192
  methodology: { type: "string", description: "Forecasting methodology used" }
10140
10193
  },
10194
+ // FrameworkExerciseProperties: Framework exercise: one run of a framework over a set of entities.
10195
+ framework_exercise: {
10196
+ 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." },
10197
+ 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)." }
10198
+ },
10141
10199
  // FunnelProperties: Funnel entity.
10142
10200
  funnel: {
10143
10201
  funnel_type: { type: "string", enum: ["acquisition", "activation", "retention", "revenue", "referral", "custom"], description: "Which stage of the customer lifecycle this funnel measures" },
@@ -19756,7 +19814,7 @@ var PORTFOLIO_GUIDE = {
19756
19814
  var WORKSPACE_GUIDE = {
19757
19815
  domain_id: "workspace",
19758
19816
  anchor_entity: "workspace",
19759
- creation_sequence: ["workspace"],
19817
+ creation_sequence: ["workspace", "framework_exercise"],
19760
19818
  patterns: [],
19761
19819
  required_bridges: [],
19762
19820
  anti_patterns: [
@@ -24764,7 +24822,7 @@ var NODE_KEY_ORDER = [
24764
24822
  "sort_order",
24765
24823
  "properties"
24766
24824
  ];
24767
- var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence"];
24825
+ var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "properties"];
24768
24826
  var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence"];
24769
24827
  var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
24770
24828
  function canonicalNode(node) {
@@ -24776,7 +24834,8 @@ function canonicalNode(node) {
24776
24834
  }
24777
24835
  function canonicalEdge(edge) {
24778
24836
  return orderedObject(edge, EDGE_KEY_ORDER, {
24779
- forceKeys: ["id", "source", "target", "type"]
24837
+ forceKeys: ["id", "source", "target", "type"],
24838
+ openKeys: ["properties"]
24780
24839
  });
24781
24840
  }
24782
24841
  function canonicalCrossEdge(edge) {
@@ -24930,7 +24989,7 @@ function isCanonical(text) {
24930
24989
  }
24931
24990
 
24932
24991
  // src/index.ts
24933
- var UPG_VERSION = "0.8.2";
24992
+ var UPG_VERSION = "0.8.4";
24934
24993
  var UPG_FORMAT_VERSION = "0.4.0";
24935
24994
  var MARKDOWN_FORMAT_VERSION = "0.1";
24936
24995
  var UPG_TYPES = getTypes();
@@ -25079,6 +25138,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
25079
25138
  collectSlugsForType,
25080
25139
  compareVersions,
25081
25140
  computeBodyChecksum,
25141
+ edgeCarriesProperties,
25082
25142
  evaluateAntiPatterns,
25083
25143
  findInvalidStatusMigrationTargets,
25084
25144
  formatUpgText,