@unified-product-graph/core 0.9.12 → 0.9.13
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 +16 -0
- package/dist/index.cjs +47 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -2
- package/dist/index.d.ts +23 -2
- package/dist/index.js +47 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.9.13] - 2026-06-11
|
|
11
|
+
|
|
12
|
+
**Foundations follow-ups: a registry-edge authoring path, specification stewardship, and three portfolio-scoped anti-patterns.** The deferred half of the foundations work (0.9.12) lands: canonical entities can now relate to one another in the registry, a specification can name its steward, and the registry surfaces the ways a foundations tier goes wrong across a portfolio.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`create_registry_edge`** (tools 119 to 120, local only): the authoring path for canonical-internal edges between registry entities, activating the reserved `registry.edges`. A registry `specification` can now be `governed_by` a registry `organization`, a `primitive` `defined_by` a `specification`, a `specification` extend another, all without touching a product graph. Validates that both endpoints exist in the registry, the type is a real catalogue edge, and the catalogue source and target types match the pair. Idempotent.
|
|
16
|
+
- **`specification_governed_by_organization`** catalogue edge (edge count 979 to 980): a registry specification's stewardship, an organization as its governing body. Organizations are registry-hostable canonicals (`define_canonical_entity` already accepts the type), so the steward is a first-class entity, not a free-text field.
|
|
17
|
+
- **Three portfolio-scoped anti-patterns** (`scope: 'portfolio'`, evaluated by `portfolio_validate`, surfaced in a new `portfolio_anti_patterns` block): `specification-without-implementer` (a registry specification no product, feature, or api_contract implements or conforms to), `primitive-scattered-without-canonical` (the same primitive title appears as a product-local node in two or more products with no registry canonical unifying them), and `product-reimplements-specification` (two or more products independently implement the same registry specification). These read the registry plus cross-product edges, context a single graph cannot express.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **`UPGCuratedAntiPattern` gains an optional `scope`** (`'graph'` default, or `'portfolio'`), and `structured_condition` is now optional. The single-graph evaluator skips portfolio-scoped patterns, so a portfolio pattern can never flip one product graph invalid; their detectors live in `portfolio_validate` instead. Anti-pattern count 15 to 18.
|
|
21
|
+
|
|
22
|
+
No entity, domain, region, or playbook count change (entities 315, domains 37, regions 11, playbooks 13).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
10
26
|
## [0.9.12] - 2026-06-10
|
|
11
27
|
|
|
12
28
|
**Foundations: a first-class `specification` and `primitive` entity, plus a technical-domain enum widening.** Two governance moats that product orgs usually mismodel as products or scatter across features now have a home, and four engineering enums that real authoring outgrew are widened.
|
package/dist/index.cjs
CHANGED
|
@@ -3137,7 +3137,9 @@ var UPG_EDGE_CATALOG = {
|
|
|
3137
3137
|
specification_extends_specification: { forward_verb: "extends", reverse_verb: "extended_by", classification: "hierarchy", source_type: "specification", target_type: "specification" },
|
|
3138
3138
|
specification_competes_with_specification: { forward_verb: "competes_with", reverse_verb: "competes_with", classification: "semantic", source_type: "specification", target_type: "specification" },
|
|
3139
3139
|
primitive_defined_by_specification: { forward_verb: "defined_by", reverse_verb: "defines", classification: "semantic", source_type: "primitive", target_type: "specification" },
|
|
3140
|
-
primitive_composes_primitive: { forward_verb: "composes", reverse_verb: "composed_by", classification: "hierarchy", source_type: "primitive", target_type: "primitive" }
|
|
3140
|
+
primitive_composes_primitive: { forward_verb: "composes", reverse_verb: "composed_by", classification: "hierarchy", source_type: "primitive", target_type: "primitive" },
|
|
3141
|
+
// Stewardship: a registry specification is governed by a (registry-hostable) organization (0.9.13).
|
|
3142
|
+
specification_governed_by_organization: { forward_verb: "governed_by", reverse_verb: "governs", classification: "semantic", source_type: "specification", target_type: "organization" }
|
|
3141
3143
|
};
|
|
3142
3144
|
var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
3143
3145
|
// Universal semantic verbs
|
|
@@ -24373,6 +24375,47 @@ var UPG_ANTI_PATTERNS = [
|
|
|
24373
24375
|
stages: ["beta", "launch", "growth", "mature"],
|
|
24374
24376
|
severity: "medium",
|
|
24375
24377
|
source: { kind: "practitioner", attribution: "Marty Cagan, Inspired (product discovery)" }
|
|
24378
|
+
},
|
|
24379
|
+
// ── Foundations layer (0.9.13): portfolio-scoped, registry-aware ──────────
|
|
24380
|
+
// These read the shared registry + cross-product edges, so they are evaluated
|
|
24381
|
+
// by portfolio_validate, not the single-graph evaluator. They carry no
|
|
24382
|
+
// structured_condition (the detector is cross-product, not a single-graph
|
|
24383
|
+
// IntelligenceCondition).
|
|
24384
|
+
{
|
|
24385
|
+
id: "specification-without-implementer",
|
|
24386
|
+
since: "0.9.13",
|
|
24387
|
+
scope: "portfolio",
|
|
24388
|
+
name: "Specification without implementer",
|
|
24389
|
+
description: "A specification in the shared registry has no product, feature, or api_contract implementing or conforming to it anywhere in the portfolio. A specification nobody implements is a document, not a contract: it states an intent the portfolio never honours.",
|
|
24390
|
+
why_it_matters: "An unimplemented specification carries authority it has not earned. Teams cite it as a standard while no surface actually conforms, so conformance claims cannot be trusted or traced.",
|
|
24391
|
+
remediation: "Link at least one product, feature, or api_contract to the specification via `product_implements_specification`, `product_exposes_specification`, `feature_conforms_to_specification`, or `api_contract_speaks_specification`; or retire the specification from the registry.",
|
|
24392
|
+
stages: ["validation", "build", "beta", "launch", "growth", "mature"],
|
|
24393
|
+
severity: "medium",
|
|
24394
|
+
source: { kind: "fundamental" }
|
|
24395
|
+
},
|
|
24396
|
+
{
|
|
24397
|
+
id: "primitive-scattered-without-canonical",
|
|
24398
|
+
since: "0.9.13",
|
|
24399
|
+
scope: "portfolio",
|
|
24400
|
+
name: "Primitive scattered without a canonical",
|
|
24401
|
+
description: "The same primitive concept appears as a product-local node in two or more products, but no canonical primitive in the shared registry unifies them. Each product redefines the building block on its own terms, so the portfolio carries several drifting copies of one shared idea instead of a single authoritative definition.",
|
|
24402
|
+
why_it_matters: "Scattered primitives drift apart in name, shape, and meaning. Cross-product reasoning breaks because the same concept reads as several unrelated entities, and a change to the shared building block has no single place to land.",
|
|
24403
|
+
remediation: "Define the shared primitive once in the registry with `define_canonical_entity`, then link each product copy via `register_instance` so the building block has one authoritative definition.",
|
|
24404
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
24405
|
+
severity: "medium",
|
|
24406
|
+
source: { kind: "fundamental" }
|
|
24407
|
+
},
|
|
24408
|
+
{
|
|
24409
|
+
id: "product-reimplements-specification",
|
|
24410
|
+
since: "0.9.13",
|
|
24411
|
+
scope: "portfolio",
|
|
24412
|
+
name: "Specification reimplemented across products",
|
|
24413
|
+
description: "Two or more products independently implement the same registry specification rather than one depending on a shared implementation. Parallel implementations of a single contract multiply the surface that must stay in sync and usually signal a missing shared library or service.",
|
|
24414
|
+
why_it_matters: "Every independent reimplementation of a specification is another place a conformance bug can hide and another copy that drifts from the contract. The cost of a spec change scales with the number of reimplementers.",
|
|
24415
|
+
remediation: "Consolidate onto one implementation that the others depend on (`depends_on_product` / `hosts`), or confirm the duplication is deliberate and record why. Capture the stewarding organization with `create_registry_edge` so the contract has an owner.",
|
|
24416
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
24417
|
+
severity: "low",
|
|
24418
|
+
source: { kind: "fundamental" }
|
|
24376
24419
|
}
|
|
24377
24420
|
];
|
|
24378
24421
|
function getAntiPatternById(id) {
|
|
@@ -24396,6 +24439,7 @@ function evaluateAntiPatterns(inputs, options) {
|
|
|
24396
24439
|
const idFilter = options?.anti_pattern_ids ? new Set(options.anti_pattern_ids) : void 0;
|
|
24397
24440
|
const fires = [];
|
|
24398
24441
|
for (const ap of UPG_ANTI_PATTERNS) {
|
|
24442
|
+
if (ap.scope === "portfolio" || !ap.structured_condition) continue;
|
|
24399
24443
|
if (severityFilter && ap.severity !== severityFilter) continue;
|
|
24400
24444
|
if (idFilter && !idFilter.has(ap.id)) continue;
|
|
24401
24445
|
if (inputs.productStage && !ap.stages.includes(inputs.productStage)) {
|
|
@@ -24556,6 +24600,7 @@ function buildViolation(ap) {
|
|
|
24556
24600
|
}
|
|
24557
24601
|
function collectTargetEntities(cond) {
|
|
24558
24602
|
const types = /* @__PURE__ */ new Set();
|
|
24603
|
+
if (!cond) return [];
|
|
24559
24604
|
walk(cond);
|
|
24560
24605
|
return [...types].sort();
|
|
24561
24606
|
function walk(c) {
|
|
@@ -27432,7 +27477,7 @@ function isCanonical(text) {
|
|
|
27432
27477
|
}
|
|
27433
27478
|
|
|
27434
27479
|
// src/index.ts
|
|
27435
|
-
var UPG_VERSION = "0.9.
|
|
27480
|
+
var UPG_VERSION = "0.9.13";
|
|
27436
27481
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
27437
27482
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
27438
27483
|
var UPG_TYPES = getTypes();
|