@unified-product-graph/core 0.28.0 → 0.30.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,54 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.30.0] - 2026-08-15
11
+
12
+ **A surface tree is often not one tree but a family of trees selected by configuration, and the graph can now say so.** A feature flag, a plan tier, a permission level or a beta programme changes which surfaces exist and what contains what; until now every surface fact was implicitly qualified by "in whatever configuration someone happened to be looking at", unrecorded. The stored graph is now the union of that family, a single configuration is a projection of it, unqualified facts are invariant, and a graph declaring no variance is semantically identical to before. Zero migration; additive; no breaking change.
13
+
14
+ ### Added
15
+ - **`configuration_axis` entity** (`proposed`, `ent_360`) — one node per semantic lever, with a closed `values` list, a `default_value`, and a `kind` (`feature_flag | plan_tier | permission_level | beta_program | other`). One axis per lever, not one per code flag: the axis names what the team pulls, the flag names how the code implements it.
16
+ - **Four edges.** `product_defines_configuration_axis` (hierarchy — the axis renders in the tree), `surface_varies_by_configuration_axis` (carries `present_under: [values]`; no edge means invariant), `feature_flag_drives_configuration_axis` (mechanism vs lever), and `surface_alternates_with_surface` ("one of these, depending" — validated against the axis declarations, direction by convention only).
17
+ - **`active_when` qualifier** on `surface_contains_surface` and `feature_occupies_surface` — and deliberately on no other edge: conditional composition is the question this release answers, and a general modality system is not. The scope is enforced: a qualifier on any other edge type is a `configuration_drift` error.
18
+ - **Projection operator** — node exclusion, then edge deactivation, then dangling removal; commutative across axes; identity on graphs declaring no variance.
19
+ - **`configuration_drift` validate scope** — ten checks covering unresolved axes, empty value lists, illegal qualifiers, contradictory alternations, and projection-stranded children. Error-severity drift gates `valid` and `structurally_valid`.
20
+ - **Per-projection anti-pattern evaluation.** Findings true everywhere report unqualified, exactly as before. Findings true in only some configurations report once, annotated (`configurations: [{axis, value}]`). Findings true only in the union are suppressed and counted (`suppressed_union_artifacts`) — they are superposition artifacts, the double-count class 0.28.0 removed. Findings true only in a projection are surfaced — a check may legitimately fire for one plan tier and no other, and the union cannot see it.
21
+ - **`configuration` parameter** on `query`, `get_tree`, and `validate_graph` — read or validate one member of the family. Every other tool rejects the argument by name rather than silently reading the union. Configuration-vs-configuration diffing rides `query`'s existing `diff_from`, which now reports edge deltas when `edge_include` is set. Tool count unchanged at 98.
22
+
23
+ ### Changed
24
+ - The one-named-configuration convention (published with 0.29.0) becomes the documented fallback for graphs that do not declare variance: the configuration you named is the projection you were implicitly describing, and nothing needs migrating when you begin declaring axes.
25
+
26
+ ### Notes
27
+ - Truth: 322 entities · 1073 edges · 69 cross-edges · 98 tools. Zero new anti-patterns — the drift checks carry the invariants, and a detector with no field evidence behind it is how a check family gets noisy.
28
+
29
+ **The contention check learns to read `capacity`, violations learn to name their nodes, and a batch write learns to un-say a property.** All three came from the same field reporter measuring the check on a real 43-surface graph: 10 surfaces flagged, 7 correctly, 3 falsely — and every false positive had an occupant count at or below its stated capacity. A surface whose occupants all fit is partitioned, not contended. Additive; no breaking change.
30
+
31
+ ### Changed
32
+ - **`contended-surface-without-arbitration` is capacity-aware.** The rule-absence branch now fires per surface only when `occupancy > (capacity ?? 1)` — occupants within a stated capacity are partitioned by design, and an unbounded surface (absent capacity) with several occupants still fires, because no stated limit means no stated answer. Contention is *displacement* (who is not rendered), which capacity settles; *ordering* of coexisting occupants remains guidance on `composition_mode: 'additive'`, deliberately not a second detector.
33
+ - **Violations carry `target_node_ids`.** Attribution rides the same collector aggregates as the verdicts: a fired check names the offending nodes instead of only the entity types, and `get_anti_pattern_violations_for` matches by id exactly for the types the id list covers (`matched_by: 'node' | 'type'`), keeping type-level reach for types never attributed. The payload reserves an empty `configurations` field for per-projection attribution (0.30.0).
34
+
35
+ ### Added
36
+ - **`edge_count_vs_property` check form** — a general per-node comparison of an edge count against a numeric property with a declared absence default. Deliberately not a one-off: the form fits any place the graph states a numeric intent.
37
+ - **`unset_properties` on `batch_update_nodes`** — batch parity with `update_node`'s existing affordance, so returning a property to *absent* no longer requires literal nulls. For `capacity` the distinction is load-bearing: absent (unbounded), `0` (reserved), and `null` are three different states.
38
+ - **`product.described_configuration`** (optional string) — names the configuration a graph describes, the typed home for the one-named-configuration convention published alongside this release. Naming, not configuring: a label for readers, read by no tool.
39
+
40
+ ### Notes
41
+ - Backfilled below: 0.28.0 shipped 2026-08-13 without a changelog entry; it is recorded now so this entry does not sit on a gap.
42
+
43
+ ---
44
+
45
+ ## [0.28.0] - 2026-08-13
46
+
47
+ **Four field-data additions teach `surface` to tell the truth about multiplicity, composition, arbitration, and drift.** All four came from one reporter populating 0.27.0's `surface` against a real 30-surface product (three parallel code audits, 329 file:line citations) before writing a single node. Additive; no breaking change. *(Backfilled 2026-08-15 — this release shipped without an entry.)*
48
+
49
+ ### Added
50
+ - **`cardinality`** (`1 | 0..1 | 1..n | 0..n`) and **`instance_scope`** (`global | per_parent`) — `capacity` counts occupants per instance; these count instances. Without them the graph says "the product has an inspector panel" when the truth is "each document pane has its own."
51
+ - **`composition_mode`** (`exclusive | additive | chained`) — how co-occupants relate. `chained` (each occupant wraps the next) is a trust model, not contention, and is exempt from `contended-surface-without-arbitration` — declare-to-earn: an unset `composition_mode` still fires.
52
+ - **`arbitration_state`** (`enforced_documented | enforced_undocumented | safe_by_coincidence | none`) — routes remediation instead of guessing it. `safe_by_coincidence` and `none` fire the contention check on their own admission; a written rule cannot mask them.
53
+ - **`surface_deviates_via_technical_debt_item`** edge — `capacity` always means *intent*, and drift from it becomes a trackable, assignable debt item instead of a lie or an endorsement.
54
+ - **Except-form on the presence filter** — `filter: { property, present, except_property, except_value }`, needed because the chained exemption is an intersection the marginal forms could not express.
55
+
56
+ ---
57
+
10
58
  ## [0.27.0] - 2026-08-12
11
59
 
12
60
  **A `surface` entity gives the UI a place: what occupies it, and who wins when two things want it.** The catalog could say who OWNS a thing (`feature_area`) and what ARCHITECTURE it belongs to (`bounded_context`), but nothing answered *what else occupies the same place?*. `screen` is route-level (route / viewport / access_level) and too coarse for a shell, a pane, a slot, a field gutter, an action bar, or an overlay. Those places are contested, and the arbitration rule is decided constantly and then rediscovered forever, because prose is where it lands. `surface` records the place, its guest list, and the rule. Additive; no breaking change.
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  The TypeScript core for the **Unified Product Graph (UPG)**, an open specification that gives product tools a shared vocabulary for entities, edges, and frameworks.
9
9
 
10
- 321 entity types across 37 domains. 1069 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
10
+ 322 entity types across 37 domains. 1073 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
11
11
 
12
12
  ## Install
13
13