@unified-product-graph/core 0.9.4 → 0.9.6
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 +28 -1
- package/dist/index.cjs +25 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -4
- package/dist/index.d.ts +59 -4
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,34 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
## [0.9.
|
|
10
|
+
## [0.9.6] - 2026-06-10
|
|
11
|
+
|
|
12
|
+
**Canonical shared-entity registry + drift detection (canonical-registry initiative, Phases 2–3).** The core of the registry: a portfolio can now define a shared entity ONCE and have every product's local copy link to it as a canonical instance, with drift surfaced automatically. Decision: `2026-06-10-canonical-shared-entity-registry.md`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`instance_of` cross-edge** (9th member of `UPG_CROSS_EDGE_TYPES`): a directed *product entity → canonical (registry) entity* relationship — "this product node is an instance of that shared, authoritative node." Endpoints must share a type (a `persona` instance_of a `persona`); the target is addressed as `registry/{node_id}`. Distinct from the symmetric `shares_*` peer edges and coexists with them (canonical-to-instance vs peer-equivalence; neither deprecates the other). Unlocks rollup ("every Developer instance and its per-surface jobs"), cross-instance diff, and drift detection.
|
|
16
|
+
- **`registry` section on the portfolio document** (`UPGPortfolioDocument.registry`, shape `UPGRegistry`): the shared-vocabulary tier that sits above products. Canonical entities are normal `UPGBaseNode`s (no new type, no flag — canonical-ness is conferred by living in the registry). Optional and additive: portfolios without a registry stay valid and byte-identical; an empty registry is omitted on serialise. `REGISTRY_PRODUCT_ID = 'registry'` is the reserved pseudo product-id used to qualify registry references; product creation rejects it.
|
|
17
|
+
- (SDK) `UPGPortfolioStore` registry accessors: `getRegistry` / `ensureRegistry` / `addRegistryNode` / `getRegistryNode` / `listRegistryNodes(type?)` / `removeRegistryNode`. The canonical serialiser round-trips the registry section (write + normalise).
|
|
18
|
+
- (MCP server) three local registry tools: **`define_canonical_entity`** (create a canonical node in the registry), **`register_instance`** (create an `instance_of` edge from a product node to a canonical, enforcing the same-type constraint and the `registry/` target), and **`list_registry`** (list canonical entities, optionally with their instances). Tool surface 110 → 113. Local-only (portfolio.upg is a workspace concept; no cloud analogue).
|
|
19
|
+
- (MCP server) **registry drift detection in `portfolio_validate`** (Phase 3): flags an `instance_of` edge whose target is missing from the registry, whose endpoints disagree on type, or whose instance title diverges from its canonical (off-canon rename). The payoff of the registry — a renamed-off-canon instance is now *detectable*, not silent.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `create_cross_product_edge` and `batch_create_cross_product_edges` reject `instance_of`, directing callers to `register_instance` (single enforcement path for the registry's same-type + `registry/` target rules). `list_cross_edge_types` now reports nine types.
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
- Phase 4 (`primitive` / `standard` type for foundational shared tech) remains deferred pending a coverage pass against existing `dependency` / `capability`; it must not block the registry core. No entity / domain / edge-catalogue count change (cross-edges are a separate union from `UPG_EDGE_CATALOG`).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## [0.9.5] - 2026-06-10
|
|
30
|
+
|
|
31
|
+
**Persona `audience_role` (canonical-registry initiative, Phase 1).** A small, independent property change — the first cut of the canonical shared-entity registry initiative. Decision: `2026-06-10-canonical-shared-entity-registry.md`.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- `PersonaProperties.audience_role`: optional enum `'buyer' | 'user' | 'champion' | 'influencer' | 'partner'` — the decision-making-unit (buying-committee) split. A portfolio must separate the economic **buyer** (who signs) from the practitioner **user** (who uses); they are distinct personas with distinct jobs, and flattening them loses the most important B2B distinction. Closed set so roles compare across products. Backward-compatible: optional, existing personas unaffected. No entity / edge / tool count change.
|
|
35
|
+
|
|
36
|
+
### Notes
|
|
37
|
+
- This unblocks the registry initiative's stopgap migration (each canonical persona gets tagged with `audience_role`). The registry core — an `instance_of` cross-edge + portfolio-tier canonical hosting + drift detection — is designed (Phases 2–4) and sequenced for later cuts.
|
|
11
38
|
|
|
12
39
|
**Cross-product structure clone + pre-commit preview + target-profile coverage (batch-4, part 2).** No core spec change — one new local MCP tool plus two opt-in parameters. Decision: `2026-06-09-cross-product-structure-clone.md`.
|
|
13
40
|
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
PRODUCT_DELIVERY_PLAYBOOK: () => PRODUCT_DELIVERY_PLAYBOOK,
|
|
36
36
|
PROPERTY_SCALE_MAP: () => PROPERTY_SCALE_MAP,
|
|
37
37
|
REFLECT_MODES: () => REFLECT_MODES,
|
|
38
|
+
REGISTRY_PRODUCT_ID: () => REGISTRY_PRODUCT_ID,
|
|
38
39
|
STRATEGY_OUTCOMES_PLAYBOOK: () => STRATEGY_OUTCOMES_PLAYBOOK,
|
|
39
40
|
UPG_ACTIVE_TYPES: () => UPG_ACTIVE_TYPES,
|
|
40
41
|
UPG_ANTI_PATTERNS: () => UPG_ANTI_PATTERNS,
|
|
@@ -2968,8 +2969,10 @@ var UPG_CROSS_EDGE_TYPES = [
|
|
|
2968
2969
|
"cannibalises",
|
|
2969
2970
|
"succeeds",
|
|
2970
2971
|
"hosts",
|
|
2971
|
-
"contributes_to"
|
|
2972
|
+
"contributes_to",
|
|
2973
|
+
"instance_of"
|
|
2972
2974
|
];
|
|
2975
|
+
var REGISTRY_PRODUCT_ID = "registry";
|
|
2973
2976
|
|
|
2974
2977
|
// src/registry/entity-type-resolution.ts
|
|
2975
2978
|
var TYPES = getTypes();
|
|
@@ -10877,7 +10880,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
10877
10880
|
experience_level: { type: "string", enum: ["beginner", "intermediate", "advanced", "varies"], description: "How experienced this persona is in their domain" },
|
|
10878
10881
|
motivation: { type: "string", description: 'Primary motivation or driving need @example "Making confident, evidence-based decisions"' },
|
|
10879
10882
|
tech_comfort: { type: "string", enum: ["low", "medium", "high", "expert", "other"], description: "Tech comfort. Closed set so personas across products compare on the same axis. Free-text colour belongs in `context` or `motivation`." },
|
|
10880
|
-
domain_expertise: { type: "string", description: 'Industry or domain knowledge this persona brings @example "10+ years SaaS experience", "New to healthcare IT"' }
|
|
10883
|
+
domain_expertise: { type: "string", description: 'Industry or domain knowledge this persona brings @example "10+ years SaaS experience", "New to healthcare IT"' },
|
|
10884
|
+
audience_role: { type: "string", enum: ["buyer", "user", "champion", "influencer", "partner"], description: 'Role in the buying / adoption decision (the decision-making-unit split): who signs (buyer) vs who uses (user) vs who advocates internally (champion) vs who shapes the choice (influencer) vs who delivers / implements (partner). A portfolio must separate the economic buyer from the practitioner user; they are distinct personas with distinct jobs. Closed set so roles compare across products. @example "buyer"' }
|
|
10881
10885
|
},
|
|
10882
10886
|
// PipelineSalesProperties: Sales pipeline.
|
|
10883
10887
|
pipeline_sales: {
|
|
@@ -26104,7 +26108,16 @@ function portfolioBody(doc) {
|
|
|
26104
26108
|
product_areas: [...doc.product_areas ?? []].sort((a, b) => byCodeUnit(a.id ?? "", b.id ?? "")).map((a) => orderedObject(a, ["id", "title", "description", "parent_area_id", "strategic_priority", "products"], { forceKeys: ["id", "title"] })),
|
|
26105
26109
|
portfolios: [...doc.portfolios ?? []].sort((a, b) => byCodeUnit(a.id ?? "", b.id ?? "")).map((p) => orderedObject(p, ["id", "title", "description", "parent_portfolio_id", "hierarchy_model", "products"], { forceKeys: ["id", "title"] })),
|
|
26106
26110
|
products,
|
|
26107
|
-
cross_edges: sortEdges(doc.cross_edges ?? []).map(canonicalCrossEdge)
|
|
26111
|
+
cross_edges: sortEdges(doc.cross_edges ?? []).map(canonicalCrossEdge),
|
|
26112
|
+
// Registry tier (shared vocabulary). Emitted only when non-empty so existing
|
|
26113
|
+
// portfolio files without a registry stay byte-identical. Canonical entities
|
|
26114
|
+
// are normal nodes, serialised with the same node/edge canonical rules.
|
|
26115
|
+
...doc.registry && doc.registry.nodes.length > 0 ? {
|
|
26116
|
+
registry: {
|
|
26117
|
+
nodes: sortNodes(doc.registry.nodes).map(canonicalNode),
|
|
26118
|
+
...doc.registry.edges && doc.registry.edges.length > 0 ? { edges: sortEdges(doc.registry.edges).map(canonicalEdge) } : {}
|
|
26119
|
+
}
|
|
26120
|
+
} : {}
|
|
26108
26121
|
};
|
|
26109
26122
|
}
|
|
26110
26123
|
function computeBodyChecksum(doc) {
|
|
@@ -26204,6 +26217,13 @@ function normalizeDocument(obj) {
|
|
|
26204
26217
|
})),
|
|
26205
26218
|
cross_edges: raw.cross_edges ?? []
|
|
26206
26219
|
};
|
|
26220
|
+
const rawRegistry = raw.registry;
|
|
26221
|
+
if (rawRegistry && Array.isArray(rawRegistry.nodes)) {
|
|
26222
|
+
out2.registry = {
|
|
26223
|
+
nodes: rawRegistry.nodes.map((n) => repairNodeDrift(n)),
|
|
26224
|
+
...Array.isArray(rawRegistry.edges) ? { edges: rawRegistry.edges } : {}
|
|
26225
|
+
};
|
|
26226
|
+
}
|
|
26207
26227
|
return out2;
|
|
26208
26228
|
}
|
|
26209
26229
|
const out = {
|
|
@@ -26226,7 +26246,7 @@ function isCanonical(text) {
|
|
|
26226
26246
|
}
|
|
26227
26247
|
|
|
26228
26248
|
// src/index.ts
|
|
26229
|
-
var UPG_VERSION = "0.9.
|
|
26249
|
+
var UPG_VERSION = "0.9.6";
|
|
26230
26250
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
26231
26251
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
26232
26252
|
var UPG_TYPES = getTypes();
|
|
@@ -26306,6 +26326,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
26306
26326
|
PRODUCT_DELIVERY_PLAYBOOK,
|
|
26307
26327
|
PROPERTY_SCALE_MAP,
|
|
26308
26328
|
REFLECT_MODES,
|
|
26329
|
+
REGISTRY_PRODUCT_ID,
|
|
26309
26330
|
STRATEGY_OUTCOMES_PLAYBOOK,
|
|
26310
26331
|
UPG_ACTIVE_TYPES,
|
|
26311
26332
|
UPG_ANTI_PATTERNS,
|