@unified-product-graph/core 0.9.9 → 0.9.10

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,25 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.9.10] - 2026-06-10
11
+
12
+ **Tooling DX + a born-valid product (batch 6, part 1) + a release-test gate.** Polish from a real multi-graph portfolio session: parity and discoverability fixes, one model correction, and — the headline — the release train now runs the unit suites, after they were found to have been silently red across a release.
13
+
14
+ ### Changed
15
+ - **Product `properties.stage` is canonical.** The stale `UPG_PROPERTY_MIGRATIONS['0.2.13']` lift of a product node's `properties.stage` to top-level `status` is retired: it contradicted the UPG-654/661 convention the whole runtime uses, so a freshly-created product read as `property_drift` against its own validator. A new product is now born valid. Legacy values are still normalised on read by `coerceProductStage`. Decision: `2026-06-10-product-stage-properties-canonical.md`.
16
+ - **`get_nodes` resolves cross-product ids.** A qualified `{product_id}/{node_id}` (the form `list_registry` / `export_edges` / cross-edges return) now reads that product's graph (read-only for non-active products), so a connective pass fetches node content across the portfolio without a `switch_product` sweep. Results carry a `product_id`.
17
+ - **`create_edge` / `batch_create_edges` accept the `p_` product-header** where the in-graph product node is expected, resolving it to the `type:"product"` node (older graphs carry a distinct `n_…` product node).
18
+ - **`did_you_mean` for a mistyped edge type** now falls back to the closest catalogue key by name (edit distance) when the endpoints don't resolve to a canonical pair — so a typo always gets a concrete suggestion.
19
+ - **`batch_create_cross_product_edges` reaches enum parity** with `create_cross_product_edge` (both now derive their type list from one spec source, so the batch tool can no longer lag a release behind — it had been missing `rolls_up_to`).
20
+ - **`batch_register_instance` echoes** `aliased` / `alias_updated` per result, so a bulk sanction pass is visible without a second `portfolio_validate`.
21
+
22
+ ### Fixed
23
+ - **Release-test gate.** The publish train now runs the train packages' unit suites (`gate:tests`) as a hard pre-flight; a red test aborts the train. This closes the gap that let a prior release ship with several silently-red suites — including import adapters that emitted edge types a rename had retired (now corrected), stale compiled test copies, and fixtures referencing retired edges.
24
+
25
+ No new tools (118) and no entity / domain / edge-catalogue count change.
26
+
27
+ ---
28
+
10
29
  ## [0.9.9] - 2026-06-10
11
30
 
12
31
  **Structural-integrity forks: validation, AI-prompt, and journey models corrected; duplicate edges collapsed.** A spec-model cut that fixes several long-standing structural mismodels and retires shadow edges. Every renamed or retired edge ships a dual-read migration (`UPG_EDGE_MIGRATIONS['0.9.9']`), so stored graphs upgrade automatically on load. No new tools (118) and no entity-count change; the within-product edge catalogue settles at 975 after the duplicate collapse.
package/dist/index.cjs CHANGED
@@ -182,6 +182,7 @@ __export(index_exports, {
182
182
  resolveEntityType: () => resolveEntityType,
183
183
  resolveLabel: () => resolveLabel,
184
184
  resolveSlugCollision: () => resolveSlugCollision,
185
+ ringOrderedDomainIds: () => ringOrderedDomainIds,
185
186
  rotateSlug: () => rotateSlug,
186
187
  serializeCanonical: () => serializeCanonical,
187
188
  validateProductStageStrict: () => validateProductStageStrict,
@@ -7666,22 +7667,18 @@ var UPG_PROPERTY_MIGRATIONS = {
7666
7667
  // graphs typically carry only one of the two; the conflict path is
7667
7668
  // theoretical but documented for predictability.
7668
7669
  "0.2.13": [
7669
- // 1. Lift properties.stage → status (with value remap to UPGProductStage).
7670
- {
7671
- kind: "lift_property_to_top_level",
7672
- type: "product",
7673
- from_property: "stage",
7674
- to: "status",
7675
- value_map: {
7676
- // Pre-canonical UPGProductStage aliases from early v0.1 product-node
7677
- // shape. "idea" was the canonical pre-validation phase before the
7678
- // 9-stage UPGProductStage shipped; today that's "concept".
7679
- idea: "concept"
7680
- // Pass-through: the canonical 9 product stages already exist as
7681
- // UPGProductStage values and need no remap when surfaced inline.
7682
- },
7683
- reason: 'lifecycle phase belongs in top-level `status` per UPGBaseNode contract, not in `properties`. Pre-canonical "idea" alias mapped to canonical `concept` (UPGProductStage[0]).'
7684
- },
7670
+ // 1. (RETIRED in 0.9.10, batch-6 #33) The product `properties.stage` →
7671
+ // top-level `status` lift is superseded by UPG-654/661, which
7672
+ // re-canonicalised a product node's lifecycle stage to
7673
+ // `properties.stage` — a distinct 9-phase `UPGProductStage` axis,
7674
+ // separate from the generic top-level `status` other entities use. The
7675
+ // entire runtime (create_product, get_graph_digest, get_product_context,
7676
+ // completeness, header-sync) reads and writes `properties.stage`, so
7677
+ // lifting it to `status` made a freshly-created product read as
7678
+ // `property_drift` against its own validator (a tool emitting drift its
7679
+ // validator immediately flags). Legacy "idea"-style values are still
7680
+ // normalised on READ by `coerceProductStage`, so retiring the lift loses
7681
+ // no cleanup. Decision: 2026-06-10-product-stage-properties-canonical.md.
7685
7682
  // 2. Rename top-level lifecycle_status → status (with value remap).
7686
7683
  {
7687
7684
  kind: "rename_top_level",
@@ -20474,19 +20471,23 @@ var UPG_DOMAIN_RINGS = [
20474
20471
  id: "define",
20475
20472
  label: "Define",
20476
20473
  description: "What are we building?",
20477
- domain_ids: ["strategy", "product_spec", "legal", "ux_design", "design_system", "brand"]
20474
+ // `business_model` lives in the `define` ring (foundational "what are we
20475
+ // building & how it sustains"), not in `grow`. This is an intentional
20476
+ // ring-vs-region divergence: UPG_REGIONS still groups business_model under
20477
+ // `business_gtm_growth` (region membership is a separately-tracked decision).
20478
+ domain_ids: ["strategy", "product_spec", "business_model", "ux_design", "design_system", "brand", "legal"]
20478
20479
  },
20479
20480
  {
20480
20481
  id: "build",
20481
20482
  label: "Build",
20482
20483
  description: "How do we construct it?",
20483
- domain_ids: ["engineering", "devops", "testing", "security", "accessibility", "data_analytics", "ai", "automation"]
20484
+ domain_ids: ["engineering", "ai", "automation", "data_analytics", "testing", "devops", "security", "accessibility"]
20484
20485
  },
20485
20486
  {
20486
20487
  id: "grow",
20487
20488
  label: "Grow",
20488
20489
  description: "How do we make money?",
20489
- domain_ids: ["business_model", "growth", "go_to_market", "pricing", "sales", "marketing"]
20490
+ domain_ids: ["pricing", "go_to_market", "sales", "marketing", "growth"]
20490
20491
  },
20491
20492
  {
20492
20493
  id: "operate",
@@ -20498,9 +20499,27 @@ var UPG_DOMAIN_RINGS = [
20498
20499
  id: "extend",
20499
20500
  label: "Extend",
20500
20501
  description: "How do we scale?",
20501
- domain_ids: ["team_org", "program_mgmt", "localisation", "ecosystem", "compliance"]
20502
+ domain_ids: ["team_org", "program_mgmt", "ecosystem", "localisation", "compliance"]
20502
20503
  }
20503
20504
  ];
20505
+ function assertRingsPartitionDomains() {
20506
+ const ringDomainIds = UPG_DOMAIN_RINGS.flatMap((r) => [...r.domain_ids]);
20507
+ const canonical = new Set(UPG_DOMAINS.map((d) => d.id));
20508
+ const ringSet = new Set(ringDomainIds);
20509
+ const duplicates = ringDomainIds.filter((id, i) => ringDomainIds.indexOf(id) !== i);
20510
+ const unknown = [...ringSet].filter((id) => !canonical.has(id));
20511
+ const unassigned = [...canonical].filter((id) => !ringSet.has(id));
20512
+ if (duplicates.length || unknown.length || unassigned.length) {
20513
+ const parts = [];
20514
+ if (unassigned.length) parts.push(`domains in no ring: [${unassigned.join(", ")}]`);
20515
+ if (unknown.length) parts.push(`ring domains not in UPG_DOMAINS: [${unknown.join(", ")}]`);
20516
+ if (duplicates.length) parts.push(`domains in multiple rings: [${[...new Set(duplicates)].join(", ")}]`);
20517
+ throw new Error(
20518
+ `UPG_DOMAIN_RINGS must partition UPG_DOMAINS exactly: ${parts.join("; ")}. Slot every domain into exactly one ring in domain-rings.ts.`
20519
+ );
20520
+ }
20521
+ }
20522
+ assertRingsPartitionDomains();
20504
20523
  function getRingForDomain(domainId) {
20505
20524
  return UPG_DOMAIN_RINGS.find((ring) => ring.domain_ids.includes(domainId));
20506
20525
  }
@@ -20508,6 +20527,9 @@ function getDomainsInRing(ringId) {
20508
20527
  const ring = UPG_DOMAIN_RINGS.find((r) => r.id === ringId);
20509
20528
  return ring ? [...ring.domain_ids] : [];
20510
20529
  }
20530
+ function ringOrderedDomainIds() {
20531
+ return UPG_DOMAIN_RINGS.flatMap((ring) => [...ring.domain_ids]);
20532
+ }
20511
20533
 
20512
20534
  // src/intelligence/domain-guides.ts
20513
20535
  var USER_GUIDE = {
@@ -27174,7 +27196,7 @@ function isCanonical(text) {
27174
27196
  }
27175
27197
 
27176
27198
  // src/index.ts
27177
- var UPG_VERSION = "0.9.9";
27199
+ var UPG_VERSION = "0.9.10";
27178
27200
  var UPG_FORMAT_VERSION = "0.4.0";
27179
27201
  var MARKDOWN_FORMAT_VERSION = "0.1";
27180
27202
  var UPG_TYPES = getTypes();
@@ -27401,6 +27423,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
27401
27423
  resolveEntityType,
27402
27424
  resolveLabel,
27403
27425
  resolveSlugCollision,
27426
+ ringOrderedDomainIds,
27404
27427
  rotateSlug,
27405
27428
  serializeCanonical,
27406
27429
  validateProductStageStrict,