@unified-product-graph/core 0.41.0 → 0.42.0

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,40 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.42.0] - 2026-10-17
11
+
12
+ **A graph could record the same relationship twice and nothing would say so.** 131 of them were sitting in this project's own graphs, `entopo.upg` included. Plus the framework definitions that could not express their own frameworks.
13
+
14
+ ### Added
15
+
16
+ - **`validate_graph` reports `duplicate_relationship_drift`.** Two shapes of one mistake. **Exact**: an identical `(source, target, type)` on several rows, which nothing distinguishes, so every row after the first inflates each edge count that reads the pair. **Shadow**: a pair carrying both a `project_contains_*` edge and `project_delivers_work_item`, the two spellings either side of 0.41.0's carrier move, which 0.41.0 accepted on the condition it become visible. Measured when it shipped: 131 exact duplicates across 160 redundant rows in 35 local graphs, and zero shadows. **Reported, never gated** — the `undeclared_property_drift` posture. Folding 131 existing instances into `structurally_valid` would flip that signal to false across every estate the day it shipped, punishing graphs for a defect nothing could previously detect. It gates once the backlog can be cleared.
17
+ - **`deduplicate_edges`** (tools 99 → 100). `kind: "exact"` (the default) keeps the first row and drops the rest. `kind: "shadow"` drops the polymorphic row where containment is also present, because 0.41.0's ruling already named containment the parent axis and the winner; the tool applies that decision rather than making one. Shadows are untouched by the default kind, so a routine dedupe can never quietly enact a ruling the caller did not ask for. `dry_run` defaults to true; it deletes edges only and never touches a node.
18
+ - **A `cycle` layout and structure pattern.** `FrameworkLayout` had eight shapes and no way to close a loop, so `build-measure-learn` and `shape-up` both declared `flow` — the only thing available. Rendered as a flow, Build → Measure → Learn is a line that stops at Learn and the framework's entire claim, that it repeats, is silently dropped. Both are canonical and both ship today.
19
+
20
+ ### Changed
21
+
22
+ - **Five framework definitions can now express their own framework.** `pirate-metrics-aarrr` declared five stages and carried ONE slot, so five stages rendered as one undifferentiated bucket; it now has a slot per stage joined by `role`, each with a `metric_category` predicate. Its stage ORDER is corrected to Acquisition, Activation, Retention, **Referral, Revenue** — not on the acronym, which cannot disambiguate three R-words, but on fidelity to the 2007 source the record itself cites. `moscow`'s four buckets existed only as `enum_values`, readable by a human and unreachable by anything projecting the framework; they are slots now. `eisenhower-matrix` and `value-vs-effort` both declared `x_axis: 'impact'`, a property neither of them defines, with their labels holding the names of two of their four cells; axes, labels and per-cell band predicates are now correct and their partitions verified exhaustive and non-overlapping. `now-next-later` was a table of three columns all bound to `title`, and `bullseye-framework` had three rings reading one entity type with nothing to tell them apart.
23
+
24
+ ### Notes
25
+
26
+ - **`user_story` keeps its lifecycle-freedom**, against a request to give it one. Pre-v0.2.7 it had a `draft → ready → in_progress → done` lifecycle; that was removed because it conflated statement-shape state with task-shape state, and the lifecycle moved onto the `task` that implements the statement. Nothing is lost on import: `user_story` declares `workflow_state`, which exists to round-trip a source tool's raw state losslessly, and the implementing task carries the state machine.
27
+
28
+ ## [0.41.1] - 2026-09-20
29
+
30
+ **Everything here is a defect an adversarial sweep of the published 0.41.0 found, and four of the five are mine.** The theme is a tool answering a confident number about a question it never actually asked.
31
+
32
+ ### Fixed
33
+
34
+ - **Product discovery never entered `.upg/products/`.** The workspace scan walked one directory level, so the folder `create_product` writes into by default was invisible. A workspace whose `workspace.json` carries `default_product` but no `products[]` therefore had `list_local_products` return `[]` and `--check` report 0, while `switch_product` on that very file succeeded: the discovery tool said nothing was there and the loading tool then loaded it. Discovery now recurses inside the workspace directory, still skipping `_`-prefixed folders, which is the distinction 0.41.0's own comment got wrong by generalising from `_archive/` (unregistered on purpose) to `products/` (the conventional home).
35
+ - **`--check` validated with a lenient parser while startup uses the strict loader.** A document with no `$upg` block, a node missing `type`, or a dangling edge all parse clean and all fail to load, so `--check` returned `ok: true` and exit 0 on workspaces the server then refused to start on. That is worse than no preflight, because an install script asserts on it and reports the environment healthy. It now loads through the same path, and a test pins that its verdict matches what starting the server actually does.
36
+ - **An unknown filter is now an error rather than an empty result.** `query({from})`, `query({traverse})`, `list_nodes({type})`, `export_edges({types})` and `migrate_status({entity_type})` each answered a clean zero for a name the spec has never heard of. The reader cannot tell "you have none of these" from "that name does not exist", and the two need opposite next moves. 0.41.0 made it live: an agent traversing the legacy `project_delivers_work_item` after the containment edges landed gets `total_edges: 0` and concludes the project has no work items. Deprecated names stay accepted, since filtering by one is exactly what a cleanup pass does.
37
+ - **`migrate_status` accepted parameters it does not declare.** `{type, from, to}` — the names its own output uses, and the ones `rename_edge_type` takes — were accepted in silence and reported a successful no-op, as were `{}` and `{zzz: 1}`. Auto mode with no arguments is legitimate, so unknown KEYS are refused rather than required ones demanded.
38
+ - **`migrate_status` would write a status `update_node` rejects.** Same type, same value, opposite outcomes, on the path that writes in bulk — so a single call could put every node of a type out of lifecycle. The target is now validated against the type's lifecycle, and the graph is left untouched when it is refused.
39
+
40
+ ### Known
41
+
42
+ - **The shadow pair is real, accepted, and not yet detectable by any tool.** 0.41.0 moved the project-to-work-item carrier, so a graph imported both before and after that release can hold `project_contains_task` and `project_delivers_work_item` for one pair. Both are individually legal, so `validate_graph` is silent; `deduplicate_nodes` is nodes-only. Until 0.42.0 adds a drift class for it, you can enumerate your own with `export_edges`: group the rows by `(source, target)` and keep only the groups whose types include BOTH a `project_contains_*` edge AND `project_delivers_work_item`. Do not simply keep every group larger than one — multiple edges between one pair are ordinarily legal, and on our own graphs that broader rule returns 172 hits and no true positives.
43
+
10
44
  ## [0.41.0] - 2026-09-04
11
45
 
12
46
  **A project could reference the work it delivered, but never hold it.** Teams model in Linear, where an Initiative holds Projects and a Project holds Issues. UPG carried the upper relation and not the lower, so a graph living alongside Linear had to explain itself twice. Plus two defects in tooling shipped earlier this week, both found by a field reporter reading output that looked clean.