@unified-product-graph/core 0.11.2 → 0.11.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 +28 -0
- package/dist/index.cjs +19 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -2
- package/dist/index.d.ts +36 -2
- package/dist/index.js +19 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,34 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.11.4] - 2026-06-14
|
|
11
|
+
|
|
12
|
+
**`get_tree` now linearises a DAG into a tree deterministically: no double-counted node, children in canonical order.** Field-testing the `journey` pattern on a fully-wired forest surfaced two structural defects, both Tier-1 (truth, not presentation — two competent agents must get the identical tree). A step reachable both directly (`user_journey -> journey_step`) and through its phase (`-> journey_phase -> journey_step`) rendered twice: once in full, once as a hollow `shared` reference — the exact mirror of the 0.9.17 silent-drop fix (G5), now silent *duplication* (J1). And children came back in storage order, ignoring the `*_order` scalars they carry, so every client had to re-sort (J2). Both are fixed by two declarative slot fields on the pattern child-map; the assembler does the linearisation once, server-side. No new tools (131); a behaviour fix on a young tool = a patch.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`order_by` on a tree pattern child slot.** Names the node scalar `get_tree` sorts the slot's children by (ascending, nodes lacking it last). Wired on the `journey` pattern: `journey_phase -> phase_order`, `journey_step -> step_order`, `journey_action -> action_order`, `screen_state -> state_order` (the UPG-663 sequence convention). Surfaced on `get_tree_pattern` introspection rows. Ordering is a property of the data, not the viewer — so the server returns children pre-sorted rather than leaving every client to re-sort (J2).
|
|
16
|
+
- **`prefer_via` on a tree pattern child slot.** Names the sibling type whose path is the canonical spine when a slot reaches the same node redundantly by two declared paths. The `journey` pattern's direct `user_journey -> journey_step` slot declares `prefer_via: journey_phase`: a step also reachable through a phase renders under the phase only. A step in no phase still renders directly (the direct path is the fallback, never a silent drop). Surfaced on `get_tree_pattern` rows (J1).
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **`get_tree` returns children in canonical order across every pattern.** Children are grouped by their declared slot position, then sorted by the slot's `order_by` scalar (stable within ties). Previously edge/storage order. A pattern with no `order_by` slots is unaffected beyond the now-deterministic slot grouping.
|
|
20
|
+
- **`get_tree` collapses a redundant DAG path instead of double-counting it.** A node reachable from one parent by two declared paths renders once, under the `prefer_via` spine. Genuine multi-parent sharing (a node under two *different* parents) is unchanged — it still renders once in full and as a `shared` reference elsewhere (the 0.9.17 G5 behaviour). Verified on the saturated journey forest: every redundant direct-plus-phase step now renders once; legitimate cross-parent shares preserved.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [0.11.3] - 2026-06-14
|
|
25
|
+
|
|
26
|
+
**A reclassification now supersedes the prior value instead of leaving a stale edge.** 0.11.0 recorded a competitor's move in the reclassification history but kept the old same-axis classify edge, so after a move the competitor was classified as BOTH the old and the new value — `get_portfolio_tree` double-counted it (Data's brief, confirmed live on 0.11.1). This release retires the prior edge as part of the move, keyed off a new axis cardinality field so a genuinely multi-select axis is never collapsed. Property add + behaviour fix = a patch.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **`cardinality` on `classification_axis` (`single` | `multi`, default `single`).** Declares whether a subject may hold several values on the axis at once. A separate concern from `axis_kind` (categorical/ordinal/continuous): an axis can be categorical yet single-select. The supersede behaviour keys off it; an axis with no `cardinality` set behaves as `single`.
|
|
30
|
+
- **`audit_axis_overlap` (new tool, 131 total; local-only).** Lists every classified source carrying more than one value on a single-select axis — the stale-edge detector for overlaps already in a graph, and the regression guard once supersede is on. A clean graph returns `overlaps: []`. Multi-select axes are exempt.
|
|
31
|
+
- **`supersede` flag on the classify writers.** `create_classification_edge` / `create_cross_product_edge` / `batch_create_cross_product_edges` (and `upg portfolio classify --no-supersede`) accept `supersede` (default true) to opt out of retiring the prior edge when an additive (multi-cell) write is genuinely wanted.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- **A same-axis classify move on a single-select axis retires the prior edge.** When a classify write supersedes a sibling classification of the same source on the same axis, the portfolio store now removes the prior edge (atomically with recording the move) so the source carries exactly one current value per single-select axis. The reclassification history is recorded either way; the response reports the retired edge under `superseded`. A `multi`-select axis keeps every value (records the move, retires nothing). This makes `diff_classification` (history) and `get_portfolio_tree` (current state) agree across reclassifications.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
10
38
|
## [0.11.2] - 2026-06-14
|
|
11
39
|
|
|
12
40
|
**Two analysis reads over the classification layer: compare two rivals, and digest a property's distribution.** The competitive tier could render the landscape (`get_portfolio_tree`), audit its completeness (`audit_property_coverage`), and diff its history (`diff_classification`), but two by-hand motions from the backfill stayed manual: comparing two competitors axis-by-axis, and counting a property's distribution over the `jq` dump. This release lands both as read tools (Data's tooling-gaps brief #5/#6). Current-state reads, no history substrate, no schema or data migration. New tools are a patch under spec policy.
|
package/dist/index.cjs
CHANGED
|
@@ -9849,6 +9849,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
9849
9849
|
// ClassificationAxisProperties: ClassificationAxis: a dimension along which subjects are classified.
|
|
9850
9850
|
classification_axis: {
|
|
9851
9851
|
axis_kind: { type: "string", enum: ["categorical", "ordinal", "continuous"], description: "Structural kind of values on this axis. `categorical` = discrete, unordered (most common; CMS architectures). `ordinal` = discrete, ordered (maturity tiers, T-shirt sizes). `continuous` = numeric range (latency budget, price points)." },
|
|
9852
|
+
cardinality: { type: "string", enum: ["single", "multi"], description: 'How many values a subject may hold on this axis at once (UPG 0.11.3). `single` (default) = a subject sits at exactly one value; a re-classification to a new value SUPERSEDES the prior one (the classify writer retires the old same-axis edge and records the move in the reclassification history). `multi` = a subject may legitimately hold several values at once (e.g. an axis like "supported frameworks"); re-classifying ADDS a value and does not supersede. A separate axis from `axis_kind`: an axis can be `categorical` (unordered) yet `single`-select, or `categorical` yet `multi`-select.' },
|
|
9852
9853
|
owner_product: { type: "string", description: "Product id when product-specific. Axes are usually product-agnostic; leave empty for shared taxonomies." }
|
|
9853
9854
|
},
|
|
9854
9855
|
// ClassificationValueProperties: ClassificationValue: a value on a classification axis.
|
|
@@ -20151,8 +20152,8 @@ function buildTypeAliases() {
|
|
|
20151
20152
|
var UPG_TYPE_ALIASES = buildTypeAliases();
|
|
20152
20153
|
|
|
20153
20154
|
// src/presentation/tree-patterns.ts
|
|
20154
|
-
var req = (type) =>
|
|
20155
|
-
var opt = (type) =>
|
|
20155
|
+
var req = (type, order_by) => order_by ? { type, required: true, order_by } : { type, required: true };
|
|
20156
|
+
var opt = (type, order_by) => order_by ? { type, order_by } : { type };
|
|
20156
20157
|
var UPG_TREE_PATTERNS = [
|
|
20157
20158
|
{
|
|
20158
20159
|
id: "ost",
|
|
@@ -20338,11 +20339,18 @@ var UPG_TREE_PATTERNS = [
|
|
|
20338
20339
|
anchor_type: "user_journey",
|
|
20339
20340
|
fallback_anchors: ["user_flow"],
|
|
20340
20341
|
child_map: {
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20342
|
+
// The phase is the grouping layer: a step reachable through a phase
|
|
20343
|
+
// renders under the phase, not twice (J1). `prefer_via` collapses the
|
|
20344
|
+
// redundant direct path; a step in NO phase still renders here. Children
|
|
20345
|
+
// are returned in `*_order` sequence (J2), not storage order.
|
|
20346
|
+
user_journey: [
|
|
20347
|
+
opt("journey_phase", "phase_order"),
|
|
20348
|
+
{ type: "journey_step", prefer_via: "journey_phase", order_by: "step_order" }
|
|
20349
|
+
],
|
|
20350
|
+
journey_phase: [opt("journey_step", "step_order")],
|
|
20351
|
+
journey_step: [opt("journey_action", "action_order"), opt("screen")],
|
|
20344
20352
|
user_flow: [opt("screen")],
|
|
20345
|
-
screen: [opt("screen_state")]
|
|
20353
|
+
screen: [opt("screen_state", "state_order")]
|
|
20346
20354
|
},
|
|
20347
20355
|
natural_depth: 3
|
|
20348
20356
|
},
|
|
@@ -20380,7 +20388,10 @@ function resolveTreePatternEdges(pattern) {
|
|
|
20380
20388
|
for (const [parent, children] of Object.entries(pattern.child_map)) {
|
|
20381
20389
|
for (const c of children) {
|
|
20382
20390
|
const e = resolvePatternEdge(parent, c.type);
|
|
20383
|
-
|
|
20391
|
+
const row = { parent, child: c.type, via: e?.via ?? null, kind: e?.kind ?? null, required: !!c.required };
|
|
20392
|
+
if (c.order_by) row.order_by = c.order_by;
|
|
20393
|
+
if (c.prefer_via) row.prefer_via = c.prefer_via;
|
|
20394
|
+
out.push(row);
|
|
20384
20395
|
}
|
|
20385
20396
|
}
|
|
20386
20397
|
return out;
|
|
@@ -28767,7 +28778,7 @@ function isCanonical(text) {
|
|
|
28767
28778
|
}
|
|
28768
28779
|
|
|
28769
28780
|
// src/index.ts
|
|
28770
|
-
var UPG_VERSION = "0.11.
|
|
28781
|
+
var UPG_VERSION = "0.11.4";
|
|
28771
28782
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
28772
28783
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
28773
28784
|
var UPG_TYPES = getTypes();
|