@unified-product-graph/core 0.27.0 → 0.29.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 +31 -0
- package/README.md +1 -1
- package/dist/index.cjs +361 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +550 -7
- package/dist/index.d.ts +550 -7
- package/dist/index.js +352 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.29.0] - 2026-08-15
|
|
11
|
+
|
|
12
|
+
**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.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **`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.
|
|
16
|
+
- **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).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`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.
|
|
20
|
+
- **`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.
|
|
21
|
+
- **`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.
|
|
22
|
+
|
|
23
|
+
### Notes
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## [0.28.0] - 2026-08-13
|
|
29
|
+
|
|
30
|
+
**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.)*
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **`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."
|
|
34
|
+
- **`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.
|
|
35
|
+
- **`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.
|
|
36
|
+
- **`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.
|
|
37
|
+
- **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.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
10
41
|
## [0.27.0] - 2026-08-12
|
|
11
42
|
|
|
12
43
|
**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.
|
|
10
|
+
321 entity types across 37 domains. 1069 typed edges. 46 canonical framework definitions, with more added as each is reviewed.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
package/dist/index.cjs
CHANGED
|
@@ -80,10 +80,12 @@ __export(index_exports, {
|
|
|
80
80
|
UPG_DOMAIN_RINGS: () => UPG_DOMAIN_RINGS,
|
|
81
81
|
UPG_EDGE_CATALOG: () => UPG_EDGE_CATALOG,
|
|
82
82
|
UPG_EDGE_COUNT: () => UPG_EDGE_COUNT,
|
|
83
|
+
UPG_EDGE_COUNT_SPECS: () => UPG_EDGE_COUNT_SPECS,
|
|
83
84
|
UPG_EDGE_MIGRATIONS: () => UPG_EDGE_MIGRATIONS,
|
|
84
85
|
UPG_EDGE_PAIR_MAP: () => UPG_EDGE_PAIR_MAP,
|
|
85
86
|
UPG_EDGE_TYPES: () => UPG_EDGE_TYPES,
|
|
86
87
|
UPG_ENTITY_COUNT: () => UPG_ENTITY_COUNT,
|
|
88
|
+
UPG_ENTITY_FILTER_SPECS: () => UPG_ENTITY_FILTER_SPECS,
|
|
87
89
|
UPG_ENTITY_META: () => UPG_ENTITY_META,
|
|
88
90
|
UPG_ENTITY_META_BY_ID: () => UPG_ENTITY_META_BY_ID,
|
|
89
91
|
UPG_ENTITY_META_BY_NAME: () => UPG_ENTITY_META_BY_NAME,
|
|
@@ -104,6 +106,7 @@ __export(index_exports, {
|
|
|
104
106
|
UPG_POLYMORPHIC_EDGE_KEYS: () => UPG_POLYMORPHIC_EDGE_KEYS,
|
|
105
107
|
UPG_PORTFOLIO_KINDS: () => UPG_PORTFOLIO_KINDS,
|
|
106
108
|
UPG_PORTFOLIO_SHARED_TYPES: () => UPG_PORTFOLIO_SHARED_TYPES,
|
|
109
|
+
UPG_PRESENCE_EXCEPT_SPECS: () => UPG_PRESENCE_EXCEPT_SPECS,
|
|
107
110
|
UPG_PRODUCT_STAGES: () => UPG_PRODUCT_STAGES,
|
|
108
111
|
UPG_PRODUCT_STAGE_COERCION_MAP: () => UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
109
112
|
UPG_PROPERTY_MIGRATIONS: () => UPG_PROPERTY_MIGRATIONS,
|
|
@@ -137,8 +140,11 @@ __export(index_exports, {
|
|
|
137
140
|
canBeChildOf: () => canBeChildOf,
|
|
138
141
|
checkHeaderSeal: () => checkHeaderSeal,
|
|
139
142
|
checkHeaderSealText: () => checkHeaderSealText,
|
|
143
|
+
checkToEdgeCountSpec: () => checkToEdgeCountSpec,
|
|
144
|
+
classifyEntityFilter: () => classifyEntityFilter,
|
|
140
145
|
coerceProductStage: () => coerceProductStage,
|
|
141
146
|
collectSlugsForType: () => collectSlugsForType,
|
|
147
|
+
collectTargetEntities: () => collectTargetEntities,
|
|
142
148
|
compareVersions: () => compareVersions,
|
|
143
149
|
computeBodyChecksum: () => computeBodyChecksum,
|
|
144
150
|
concernEvaluatedFor: () => concernEvaluatedFor,
|
|
@@ -150,6 +156,8 @@ __export(index_exports, {
|
|
|
150
156
|
deriveSingleCounts: () => deriveSingleCounts,
|
|
151
157
|
describeTreePattern: () => describeTreePattern,
|
|
152
158
|
edgeCarriesProperties: () => edgeCarriesProperties,
|
|
159
|
+
edgeCountSpecKey: () => edgeCountSpecKey,
|
|
160
|
+
entityFilterKey: () => entityFilterKey,
|
|
153
161
|
evaluateAntiPatterns: () => evaluateAntiPatterns,
|
|
154
162
|
findInvalidStatusMigrationTargets: () => findInvalidStatusMigrationTargets,
|
|
155
163
|
formatUpgText: () => formatUpgText,
|
|
@@ -240,6 +248,7 @@ __export(index_exports, {
|
|
|
240
248
|
normalizeDocument: () => normalizeDocument,
|
|
241
249
|
parseUpg: () => parseUpg,
|
|
242
250
|
pickCanonicalEdge: () => pickCanonicalEdge,
|
|
251
|
+
presenceExceptKey: () => presenceExceptKey,
|
|
243
252
|
resolveAllEdges: () => resolveAllEdges,
|
|
244
253
|
resolveContainmentEdge: () => resolveContainmentEdge,
|
|
245
254
|
resolveEntityType: () => resolveEntityType,
|
|
@@ -1800,9 +1809,10 @@ var UPG_EDGE_CATALOG = {
|
|
|
1800
1809
|
journey_action_realised_by_feature: { forward_verb: "realised_by", reverse_verb: "realises", classification: "cross-domain", source_type: "journey_action", target_type: "feature" },
|
|
1801
1810
|
// ── surface (0.27.0): the place inside a screen ─────────────────────────
|
|
1802
1811
|
// `screen` is route-level; `surface` is the contested place within it. The
|
|
1803
|
-
//
|
|
1812
|
+
// eleven edges below give a surface its spine (nesting), its guest list, its
|
|
1804
1813
|
// purpose, its governing rule, its rendering vocabulary, its measurement,
|
|
1805
|
-
//
|
|
1814
|
+
// its replacement path, and (0.28.0) the gap between what it intends and
|
|
1815
|
+
// what it actually does.
|
|
1806
1816
|
//
|
|
1807
1817
|
// Classification adjudications, each anchored on the nearest precedent:
|
|
1808
1818
|
// contains → hierarchy (feature_area_contains_feature_area, planning_cycle_contains_planning_cycle)
|
|
@@ -1838,6 +1848,35 @@ var UPG_EDGE_CATALOG = {
|
|
|
1838
1848
|
surface_renders_design_component: { forward_verb: "renders", reverse_verb: "rendered_on", classification: "hierarchy", source_type: "surface", target_type: "design_component", cross_product_eligible: true },
|
|
1839
1849
|
surface_measured_by_metric: { forward_verb: "measured_by", reverse_verb: "measures", classification: "semantic", source_type: "surface", target_type: "metric", cross_product_eligible: true },
|
|
1840
1850
|
surface_supersedes_surface: { forward_verb: "supersedes", reverse_verb: "superseded_by", classification: "semantic", source_type: "surface", target_type: "surface" },
|
|
1851
|
+
// ── surface, 0.28.0 addition: intent versus reality ─────────────────────────
|
|
1852
|
+
// Every surface property records what the design INTENDS. `capacity: 1` on a
|
|
1853
|
+
// banner region is an assertion, and a real product will eventually be found
|
|
1854
|
+
// rendering four. Without somewhere to put that fact, the only ways to record
|
|
1855
|
+
// it are to edit `capacity` up to 4 (which destroys the intent, and with it
|
|
1856
|
+
// any evidence that a gap exists) or to leave the graph quietly wrong.
|
|
1857
|
+
//
|
|
1858
|
+
// This edge is the third option, and it is what lets `capacity` mean intent
|
|
1859
|
+
// unconditionally: the deviation hangs off a `technical_debt_item`, which is
|
|
1860
|
+
// already a trackable, assignable, prioritisable entity with an owner and a
|
|
1861
|
+
// remediation cost. The gap becomes work rather than a discrepancy.
|
|
1862
|
+
//
|
|
1863
|
+
// classification → cross-domain (ux_design → technical_debt). Crossing INTO
|
|
1864
|
+
// the debt domain from another ring is the established shape:
|
|
1865
|
+
// risk_manifests_as_technical_debt_item is cross-domain on exactly that
|
|
1866
|
+
// reasoning. NOT `causal`: `decision_incurs_technical_debt_item` is causal
|
|
1867
|
+
// because a decision BRINGS the debt into existence, whereas a surface
|
|
1868
|
+
// does not cause its own drift, it exhibits it. NOT `hierarchy`: that
|
|
1869
|
+
// would oblige a surface → technical_debt_item containment pair in
|
|
1870
|
+
// UPG_VALID_CHILDREN (guardrail G2b), and a surface does not own the debt
|
|
1871
|
+
// that afflicts it — service_carries_technical_debt_item can be hierarchy
|
|
1872
|
+
// because a service genuinely owns its backlog.
|
|
1873
|
+
// verbs → `deviates_via` / `causes_deviation_in`. The forward verb names
|
|
1874
|
+
// what the SOURCE does (the surface deviates), qualified by the debt that
|
|
1875
|
+
// accounts for it, so the edge key reads as the sentence it asserts. The
|
|
1876
|
+
// reverse says what the debt does to the place.
|
|
1877
|
+
// not cross_product_eligible → a deviation is a fact about one product's
|
|
1878
|
+
// code, and `surface` is not portfolio_shared in the first place.
|
|
1879
|
+
surface_deviates_via_technical_debt_item: { forward_verb: "deviates_via", reverse_verb: "causes_deviation_in", classification: "cross-domain", source_type: "surface", target_type: "technical_debt_item" },
|
|
1841
1880
|
// Inbound. `feature_occupies_surface` is the guest list: the edge that makes
|
|
1842
1881
|
// "what else lives here?" answerable, and the one the contention anti-pattern
|
|
1843
1882
|
// counts. cross-domain (product_spec → ux_design), like screen_surfaces_feature
|
|
@@ -12909,7 +12948,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12909
12948
|
health_status: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Overall health" },
|
|
12910
12949
|
url: { type: "string", description: "Where the product lives. Marketing site, app store URL, etc.", modifier: "volatile" },
|
|
12911
12950
|
logo_url: { type: "string", description: "Logo or icon URL. Used to render product cards and lists.", modifier: "volatile" },
|
|
12912
|
-
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" }
|
|
12951
|
+
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" },
|
|
12952
|
+
described_configuration: { type: "string", description: `The product configuration this graph describes, named in plain language. A product's composition often differs by feature flag, plan tier, permission level or beta programme: surfaces appear, disappear, or are replaced by different surfaces with different occupants. A graph that models one of those without saying which is qualified by a condition nobody wrote down, and every fact in it inherits that silence. NAMING, NOT CONFIGURING. This is a label for readers. Nothing reads it to gate, filter, or alter how a tool interprets the graph, and it makes no claim about which configuration most customers are on. @example "Enterprise plan, split-navigation flag on" @example "Default: free tier, all flags off"` }
|
|
12913
12953
|
},
|
|
12914
12954
|
// ProductAreaProperties: ProductArea entity.
|
|
12915
12955
|
product_area: {
|
|
@@ -13437,8 +13477,12 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13437
13477
|
surface_kind: { type: "string", enum: ["shell", "tool", "pane", "region", "slot", "gutter", "action_bar", "overlay", "ambient"], description: "Structural kind. Determines what may legally nest inside this surface." },
|
|
13438
13478
|
persistence: { type: "string", enum: ["always", "conditional", "on_demand", "transient"], description: "How reliably the surface is present." },
|
|
13439
13479
|
visibility_condition: { type: "string", description: 'When the surface appears, in plain language. Pairs with `persistence: \'conditional\'`, which states *that* it is conditional; this states *what* the condition is. @example "A node is selected", "Only for workspace admins"' },
|
|
13440
|
-
capacity: { type: "number", description: 'How many occupants the surface holds at once, as a non-negative count. ABSENT MEANS UNBOUNDED. UPG has no union-typed property primitive (`PropertyDefinition.type` is a single scalar kind), so the proposed `integer | unbounded` shape is expressed as an optional number whose absence carries the "no cap" reading, rather than a magic sentinel value that every consumer would have to special-case. `0` is a real cap meaning "nothing may occupy this surface" (a reserved place), not "unbounded".' },
|
|
13480
|
+
capacity: { type: "number", description: 'How many occupants ONE INSTANCE of the surface holds at once, as a non-negative count. Count the instances themselves with `cardinality`. CAPACITY IS ALWAYS INTENT, NEVER OBSERVED BEHAVIOUR. It records the cap the design asserts, so a banner region declared `capacity: 1` keeps saying 1 even after someone finds it rendering four. Reality that has drifted from the declared intent is recorded as a trackable, assignable debt item on `surface_deviates_via_technical_debt_item`, not by quietly editing this number up to match the bug. Editing it to match would destroy the only record that a gap exists. ABSENT MEANS UNBOUNDED. UPG has no union-typed property primitive (`PropertyDefinition.type` is a single scalar kind), so the proposed `integer | unbounded` shape is expressed as an optional number whose absence carries the "no cap" reading, rather than a magic sentinel value that every consumer would have to special-case. `0` is a real cap meaning "nothing may occupy this surface" (a reserved place), not "unbounded". THREE STATES, AND THEY ARE ALL DIFFERENT. Absent = unbounded, no cap stated. `0` = a reserved place nothing may occupy. `null` = neither of those, and nothing this field defines; it is what you get by writing an explicit null rather than removing the key, and no consumer reads it as a cap. To return this property to ABSENT, remove the key with `update_node`\'s or `batch_update_nodes`\' `unset_properties`, since a property merge preserves anything you omit. UNBOUNDED IS NOT AN EXEMPTION FROM SCRUTINY. The contention detector reads an absent capacity as a threshold of 1, on the reasoning that a surface which states no limit has stated no answer either, so several occupants is exactly the unrecorded decision worth naming. Declaring a real capacity is therefore the way to quiet the check honestly, and the only way that also records something true.' },
|
|
13481
|
+
cardinality: { type: "string", enum: ["1", "0..1", "1..n", "0..n"], description: "How many instances of this surface exist. `capacity` counts occupants within one instance; this counts the instances." },
|
|
13482
|
+
instance_scope: { type: "string", enum: ["global", "per_parent"], description: 'What an instance is scoped to: one shared instance for the product, or one per containing surface. Decides whether "the product has this surface" is a true sentence or a per-parent one.' },
|
|
13483
|
+
composition_mode: { type: "string", enum: ["exclusive", "additive", "chained"], description: "How the occupants relate: one wins (`exclusive`), all coexist (`additive`), or each wraps the next (`chained`). Declaring `chained` exempts the surface from `contended-surface-without-arbitration`; leaving this unset does not." },
|
|
13441
13484
|
arbitration_rule: { type: "string", description: 'Who wins when more occupants want the surface than `capacity` allows, and why. ABSENCE IS MEANINGFUL: a null or empty `arbitration_rule` on a contested surface means nobody decided, which is exactly what the `contended-surface-without-arbitration` anti-pattern detects. Do not fill this in with a placeholder to silence the check. @example "Highest priority wins; ties break to the most recently updated."' },
|
|
13485
|
+
arbitration_state: { type: "string", enum: ["enforced_documented", "enforced_undocumented", "safe_by_coincidence", "none"], description: 'Whether the arbitration answer is enforced, written down, both, or neither. Separates "enforced in code but never transcribed" (ten minutes of typing) from "never decided" (a design meeting) from "safe only because nothing has collided yet" (the dangerous one).' },
|
|
13442
13486
|
extensibility: { type: "string", enum: ["closed", "plugin_registerable", "user_configurable"], description: "Who may add occupants to the surface." },
|
|
13443
13487
|
mutates_content: { type: "boolean", description: "Whether occupying this surface can change the underlying content, as opposed to only selecting or revealing it. The selector-versus-mutator distinction: a gutter that toggles a value is a mutator, a gutter that jumps the cursor is not." },
|
|
13444
13488
|
dimensional_constraint: { type: "string", description: 'The hard spatial budget the surface imposes on its occupants, in whatever unit the design system speaks. @example "292px wide", "25px per field", "two grid columns"' }
|
|
@@ -27275,7 +27319,106 @@ var UPG_ANTI_PATTERNS = [
|
|
|
27275
27319
|
id: "contended-surface-without-arbitration",
|
|
27276
27320
|
since: "0.27.0",
|
|
27277
27321
|
name: "Contended surface without arbitration",
|
|
27278
|
-
description: '
|
|
27322
|
+
description: 'At least one surface in this graph has no recorded answer to "who wins here, and why". Either it holds more occupants than its stated `capacity` allows (or holds several while stating no limit at all) and carries no arbitration_rule, or it admits through arbitration_state that the answer is absent (`none`) or accidental (`safe_by_coincidence`). Left unrecorded, that answer lives in whoever remembers the last argument.',
|
|
27323
|
+
// 0.28.0 (feedback 852a9721, from a 30-surface field audit). Three changes,
|
|
27324
|
+
// each earned by a reported false positive or a reported blind spot:
|
|
27325
|
+
//
|
|
27326
|
+
// (a) rule-absence, EXCEPT chained. `composition_mode: 'chained'` means each
|
|
27327
|
+
// occupant wraps the next rather than competing with it. Many occupants
|
|
27328
|
+
// and no arbitration rule is the designed shape, so the reporter's 14
|
|
27329
|
+
// chained slots were 14 standing false positives — and because
|
|
27330
|
+
// `get_anti_pattern_violations_for` attributes by TYPE, they kept the
|
|
27331
|
+
// whole surface roster lit no matter how diligently the genuinely
|
|
27332
|
+
// contended surfaces were documented. The check could never be silenced
|
|
27333
|
+
// by correct modelling, which is the property a check has to have.
|
|
27334
|
+
// Exemption is DECLARE-TO-EARN: an unset `composition_mode` still fires,
|
|
27335
|
+
// so the default posture stays suspicious and silence must be claimed.
|
|
27336
|
+
// (b) `safe_by_coincidence` fires on its own, with no chained exemption. It
|
|
27337
|
+
// is the one state that a written `arbitration_rule` can mask: the field
|
|
27338
|
+
// audit found surfaces whose prose described disjoint enum values doing
|
|
27339
|
+
// the arbitrating with nothing guarding them. Branch (a) cannot see
|
|
27340
|
+
// those, because the rule text is present.
|
|
27341
|
+
// (c) `none` fires on its own too. A graph that says "nobody decided" while
|
|
27342
|
+
// carrying rule text is contradicting itself, and the admission is the
|
|
27343
|
+
// half to believe.
|
|
27344
|
+
//
|
|
27345
|
+
// `enforced_undocumented` deliberately gets NO branch and NO suppression. It
|
|
27346
|
+
// needs none: a surface enforcing an untranscribed rule has no
|
|
27347
|
+
// `arbitration_rule`, so branch (a) already fires. Nor does it earn a
|
|
27348
|
+
// downgrade — severity is per-anti-pattern, not per-violation, so there is
|
|
27349
|
+
// nowhere to put one; and the harm this pattern names is that the settlement
|
|
27350
|
+
// is unrecorded, which is exactly what `enforced_undocumented` admits. The
|
|
27351
|
+
// graph is the record; code is not. What the state buys is triage, so the
|
|
27352
|
+
// remediation text below names the two costs separately.
|
|
27353
|
+
//
|
|
27354
|
+
// No new anti-pattern was minted for `safe_by_coincidence`. It is a value of
|
|
27355
|
+
// the arbitration question, not a different question, and a second detector
|
|
27356
|
+
// would fire on the same graphs and double-report through type-keyed
|
|
27357
|
+
// attribution — making this family noisier on its first real deployment,
|
|
27358
|
+
// which is the exact failure (a) exists to fix. If field evidence later shows
|
|
27359
|
+
// it needs its own severity, mint it then.
|
|
27360
|
+
//
|
|
27361
|
+
// 0.29.0 (feedback af9ae4c2, the same reporter's measured follow-up: 43
|
|
27362
|
+
// surfaces, 10 flagged, 7 rightly and 3 wrongly). Branch (a) moved from an
|
|
27363
|
+
// aggregate presence count to the per-node `edge_count_vs_property` form.
|
|
27364
|
+
//
|
|
27365
|
+
// (d) CAPACITY IS NOW READ. The old branch was a pure edge count: any
|
|
27366
|
+
// surface with more than one occupant and no rule. That flags a header
|
|
27367
|
+
// row declaring room for four and holding exactly four, which is not
|
|
27368
|
+
// contended but PARTITIONED — everyone fits, by design, so nothing is
|
|
27369
|
+
// displaced and there is nothing to decide. All three of the reporter's
|
|
27370
|
+
// false positives were this, and all three had occupancy at or below a
|
|
27371
|
+
// stated capacity. The rule is now `occupancy > (capacity ?? 1)`.
|
|
27372
|
+
//
|
|
27373
|
+
// ABSENT CAPACITY BEHAVES AS 1, NOT AS INFINITY. Absence means
|
|
27374
|
+
// unbounded, and it is tempting to read unbounded as "never flag". The
|
|
27375
|
+
// opposite is right: a surface that states no limit has stated no
|
|
27376
|
+
// answer, so two or more occupants is precisely the unrecorded decision
|
|
27377
|
+
// this pattern names. That keeps all four of the reporter's unbounded
|
|
27378
|
+
// true positives (7, 7, 10 and 3 occupants) firing.
|
|
27379
|
+
//
|
|
27380
|
+
// The check had to become per-node rather than two ANDed aggregates:
|
|
27381
|
+
// "some surface is over capacity" and "some surface has no rule" are
|
|
27382
|
+
// both true when they are DIFFERENT surfaces, which would have kept
|
|
27383
|
+
// every false positive alive. See `EdgeCountVsPropertyCheck.node_filter`.
|
|
27384
|
+
//
|
|
27385
|
+
// ADJUDICATED: DOES A CAPACITY-SATISFIED `additive` SURFACE STILL NEED AN
|
|
27386
|
+
// ORDERING RULE? The reporter's capacity rule and their own earlier report
|
|
27387
|
+
// pull in opposite directions here: 0.28.0 documented that `additive`
|
|
27388
|
+
// occupants raise a question of ORDER rather than victory and that the order
|
|
27389
|
+
// belongs in `arbitration_rule`, while the capacity rule says a surface
|
|
27390
|
+
// where everyone fits has nothing to settle. Ruling: BOTH ARE RIGHT, because
|
|
27391
|
+
// they are about different failures, and this detector owns only one of them.
|
|
27392
|
+
//
|
|
27393
|
+
// - Contention is about DISPLACEMENT: who is not rendered. Capacity settles
|
|
27394
|
+
// it. If everyone fits, nobody is displaced, and "who wins" is genuinely
|
|
27395
|
+
// moot. That is why reading capacity removes real false positives rather
|
|
27396
|
+
// than merely quieting the check.
|
|
27397
|
+
// - Ordering is about ARRANGEMENT: in what sequence coexisting occupants
|
|
27398
|
+
// appear. Capacity says nothing about it. The reporter's own four-occupant
|
|
27399
|
+
// header row, positioned by runtime width measurement, has a live ordering
|
|
27400
|
+
// question and no displacement question at all.
|
|
27401
|
+
//
|
|
27402
|
+
// So this check reads capacity and stops at displacement. The ordering concern
|
|
27403
|
+
// stays documented on `composition_mode: 'additive'` as guidance, NOT as a
|
|
27404
|
+
// detector: minting `additive-surface-without-ordering` today would repeat the
|
|
27405
|
+
// 0.28.0 mistake of shipping a second detector on no field evidence, and it
|
|
27406
|
+
// would double-report against this one. Mint it when a graph shows unrecorded
|
|
27407
|
+
// ordering causing a real defect, with its own severity and remediation.
|
|
27408
|
+
//
|
|
27409
|
+
// Known consequence, recorded rather than papered over: `arbitration_rule` is
|
|
27410
|
+
// now visibly OVERLOADED. It holds a displacement rule for `exclusive`
|
|
27411
|
+
// surfaces and an ordering rule for `additive` ones, and this check reads it
|
|
27412
|
+
// for only the first. Splitting it (`arbitration_rule` vs `ordering_rule`) is
|
|
27413
|
+
// the natural move if the ordering detector is ever minted; it is not worth a
|
|
27414
|
+
// breaking property change before then.
|
|
27415
|
+
//
|
|
27416
|
+
// STILL OPEN (banked at 0.28.0, deliberately NOT changed here): a `chained`
|
|
27417
|
+
// slot that honestly declares `arbitration_state: 'none'` re-fires through
|
|
27418
|
+
// branch (c), which carries no chained exemption. Current guidance stands:
|
|
27419
|
+
// leave `arbitration_state` unset on chained surfaces. Changing (b)/(c) was
|
|
27420
|
+
// out of scope for this release and still awaits the field confirmation the
|
|
27421
|
+
// reporter will produce populating their 14 chained slots.
|
|
27279
27422
|
structured_condition: {
|
|
27280
27423
|
operator: "and",
|
|
27281
27424
|
checks: [
|
|
@@ -27290,17 +27433,45 @@ var UPG_ANTI_PATTERNS = [
|
|
|
27290
27433
|
}
|
|
27291
27434
|
},
|
|
27292
27435
|
{
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
|
|
27296
|
-
|
|
27297
|
-
|
|
27298
|
-
|
|
27436
|
+
operator: "or",
|
|
27437
|
+
checks: [
|
|
27438
|
+
{
|
|
27439
|
+
check: {
|
|
27440
|
+
type: "edge_count_vs_property",
|
|
27441
|
+
entity_type: "surface",
|
|
27442
|
+
edge_type: "feature_occupies_surface",
|
|
27443
|
+
direction: "inbound",
|
|
27444
|
+
property: "capacity",
|
|
27445
|
+
property_absent_default: 1,
|
|
27446
|
+
node_comparison: "gt",
|
|
27447
|
+
node_filter: { property: "arbitration_rule", present: false },
|
|
27448
|
+
except_property: "composition_mode",
|
|
27449
|
+
except_value: "chained",
|
|
27450
|
+
comparison: "nonzero"
|
|
27451
|
+
}
|
|
27452
|
+
},
|
|
27453
|
+
{
|
|
27454
|
+
check: {
|
|
27455
|
+
type: "entity_count",
|
|
27456
|
+
entity_type: "surface",
|
|
27457
|
+
filter: { property: "arbitration_state", value: "safe_by_coincidence" },
|
|
27458
|
+
comparison: "nonzero"
|
|
27459
|
+
}
|
|
27460
|
+
},
|
|
27461
|
+
{
|
|
27462
|
+
check: {
|
|
27463
|
+
type: "entity_count",
|
|
27464
|
+
entity_type: "surface",
|
|
27465
|
+
filter: { property: "arbitration_state", value: "none" },
|
|
27466
|
+
comparison: "nonzero"
|
|
27467
|
+
}
|
|
27468
|
+
}
|
|
27469
|
+
]
|
|
27299
27470
|
}
|
|
27300
27471
|
]
|
|
27301
27472
|
},
|
|
27302
|
-
why_it_matters: "Contention over a UI place is settled every time it comes up, and an unrecorded settlement is re-argued at the next feature. The cost is paid in repeated decisions, not in a visible defect.",
|
|
27303
|
-
remediation: "
|
|
27473
|
+
why_it_matters: "Contention over a UI place is settled every time it comes up, and an unrecorded settlement is re-argued at the next feature. The cost is paid in repeated decisions, not in a visible defect. A surface that is safe only by coincidence pays it all at once instead, the first time an occupant is added.",
|
|
27474
|
+
remediation: "The violation names the offending surfaces in `target_node_ids`; start there rather than re-reading the whole roster. Read `arbitration_state` first: it says what the work actually is. `enforced_undocumented` means the rule already exists in code and needs transcribing into `arbitration_rule`: ten minutes, no meeting. `none` means nobody has decided and someone has to. `safe_by_coincidence` means nothing is enforcing anything and the surface only looks settled; that one wants a guard in code, not just prose. If a flagged surface genuinely holds everything it was designed to hold, the honest fix is to state its `capacity`, not to invent a rule: a surface whose occupancy is at or below a stated capacity is partitioned rather than contended, and it stops firing. Where a design system already answers the question, link the source with `surface_governed_by_design_guideline`. Surfaces whose occupants wrap one another rather than compete should declare `composition_mode: 'chained'`, which exempts them: that is a factual claim about how the code composes, not a way to quiet the check.",
|
|
27304
27475
|
stages: ["build", "beta", "launch", "growth", "mature", "maintenance"],
|
|
27305
27476
|
severity: "medium",
|
|
27306
27477
|
source: { kind: "fundamental" }
|
|
@@ -27358,6 +27529,127 @@ var UPG_ANTI_PATTERNS = [
|
|
|
27358
27529
|
source: { kind: "fundamental" }
|
|
27359
27530
|
}
|
|
27360
27531
|
];
|
|
27532
|
+
function walkForExceptSpecs(cond, out) {
|
|
27533
|
+
if ("operator" in cond) {
|
|
27534
|
+
for (const child of cond.checks) walkForExceptSpecs(child, out);
|
|
27535
|
+
return;
|
|
27536
|
+
}
|
|
27537
|
+
const check = cond.check;
|
|
27538
|
+
if (check.type !== "entity_count" || !check.filter) return;
|
|
27539
|
+
const f = check.filter;
|
|
27540
|
+
if (typeof f.property !== "string" || typeof f.except_property !== "string" || typeof f.except_value !== "string") {
|
|
27541
|
+
return;
|
|
27542
|
+
}
|
|
27543
|
+
const spec = {
|
|
27544
|
+
entity_type: check.entity_type,
|
|
27545
|
+
property: f.property,
|
|
27546
|
+
except_property: f.except_property,
|
|
27547
|
+
except_value: f.except_value
|
|
27548
|
+
};
|
|
27549
|
+
const seen = out.some(
|
|
27550
|
+
(s) => s.entity_type === spec.entity_type && s.property === spec.property && s.except_property === spec.except_property && s.except_value === spec.except_value
|
|
27551
|
+
);
|
|
27552
|
+
if (!seen) out.push(spec);
|
|
27553
|
+
}
|
|
27554
|
+
var UPG_PRESENCE_EXCEPT_SPECS = (() => {
|
|
27555
|
+
const out = [];
|
|
27556
|
+
for (const ap of UPG_ANTI_PATTERNS) {
|
|
27557
|
+
if (ap.structured_condition) walkForExceptSpecs(ap.structured_condition, out);
|
|
27558
|
+
}
|
|
27559
|
+
return out;
|
|
27560
|
+
})();
|
|
27561
|
+
function presenceExceptKey(property, exceptProperty, exceptValue) {
|
|
27562
|
+
return `${property}!${exceptProperty}=${exceptValue}`;
|
|
27563
|
+
}
|
|
27564
|
+
function edgeCountSpecKey(spec) {
|
|
27565
|
+
const filter = spec.node_filter ? `${spec.node_filter.property}=${spec.node_filter.present ? "present" : "absent"}` : "*";
|
|
27566
|
+
const except = spec.except_property !== void 0 && spec.except_value !== void 0 ? `${spec.except_property}=${spec.except_value}` : "*";
|
|
27567
|
+
return [
|
|
27568
|
+
spec.entity_type,
|
|
27569
|
+
spec.edge_type,
|
|
27570
|
+
spec.direction,
|
|
27571
|
+
spec.property,
|
|
27572
|
+
String(spec.property_absent_default),
|
|
27573
|
+
spec.node_comparison,
|
|
27574
|
+
filter,
|
|
27575
|
+
except
|
|
27576
|
+
].join("|");
|
|
27577
|
+
}
|
|
27578
|
+
function entityFilterKey(entityType, filter) {
|
|
27579
|
+
const kind = classifyEntityFilter(filter);
|
|
27580
|
+
switch (kind) {
|
|
27581
|
+
case "presence":
|
|
27582
|
+
return `${entityType}|${filter.property}=${filter.present ? "present" : "absent"}`;
|
|
27583
|
+
case "value":
|
|
27584
|
+
return `${entityType}|${filter.property}=${filter.value}`;
|
|
27585
|
+
case "status":
|
|
27586
|
+
return `${entityType}|status=${filter.status}`;
|
|
27587
|
+
case "unrecognized":
|
|
27588
|
+
return `${entityType}|<unrecognized>`;
|
|
27589
|
+
}
|
|
27590
|
+
}
|
|
27591
|
+
function classifyEntityFilter(filter) {
|
|
27592
|
+
const hasProperty = typeof filter.property === "string";
|
|
27593
|
+
if (hasProperty && typeof filter.present === "boolean") return "presence";
|
|
27594
|
+
if (hasProperty && typeof filter.value === "string") return "value";
|
|
27595
|
+
if (typeof filter.status === "string") return "status";
|
|
27596
|
+
return "unrecognized";
|
|
27597
|
+
}
|
|
27598
|
+
function walkForEntityFilterSpecs(cond, out) {
|
|
27599
|
+
if ("operator" in cond) {
|
|
27600
|
+
for (const child of cond.checks) walkForEntityFilterSpecs(child, out);
|
|
27601
|
+
return;
|
|
27602
|
+
}
|
|
27603
|
+
const check = cond.check;
|
|
27604
|
+
if (check.type !== "entity_count" || !check.filter) return;
|
|
27605
|
+
const kind = classifyEntityFilter(check.filter);
|
|
27606
|
+
if (kind === "unrecognized") {
|
|
27607
|
+
throw new Error(
|
|
27608
|
+
`Unrecognized entity_count filter shape on "${check.entity_type}": ${JSON.stringify(check.filter)}. Recognised shapes are { property, present }, { property, value } and { status }. Teach classifyEntityFilter + the collector before declaring a new one.`
|
|
27609
|
+
);
|
|
27610
|
+
}
|
|
27611
|
+
const key = entityFilterKey(check.entity_type, check.filter);
|
|
27612
|
+
if (out.some((s) => entityFilterKey(s.entity_type, s.filter) === key)) return;
|
|
27613
|
+
out.push({ entity_type: check.entity_type, filter: check.filter, kind });
|
|
27614
|
+
}
|
|
27615
|
+
var UPG_ENTITY_FILTER_SPECS = (() => {
|
|
27616
|
+
const out = [];
|
|
27617
|
+
for (const ap of UPG_ANTI_PATTERNS) {
|
|
27618
|
+
if (ap.structured_condition) walkForEntityFilterSpecs(ap.structured_condition, out);
|
|
27619
|
+
}
|
|
27620
|
+
return out;
|
|
27621
|
+
})();
|
|
27622
|
+
function checkToEdgeCountSpec(check) {
|
|
27623
|
+
return {
|
|
27624
|
+
entity_type: check.entity_type,
|
|
27625
|
+
edge_type: check.edge_type,
|
|
27626
|
+
direction: check.direction ?? "inbound",
|
|
27627
|
+
property: check.property,
|
|
27628
|
+
property_absent_default: check.property_absent_default,
|
|
27629
|
+
node_comparison: check.node_comparison,
|
|
27630
|
+
node_filter: check.node_filter,
|
|
27631
|
+
except_property: check.except_property,
|
|
27632
|
+
except_value: check.except_value
|
|
27633
|
+
};
|
|
27634
|
+
}
|
|
27635
|
+
function walkForEdgeCountSpecs(cond, out) {
|
|
27636
|
+
if ("operator" in cond) {
|
|
27637
|
+
for (const child of cond.checks) walkForEdgeCountSpecs(child, out);
|
|
27638
|
+
return;
|
|
27639
|
+
}
|
|
27640
|
+
const check = cond.check;
|
|
27641
|
+
if (check.type !== "edge_count_vs_property") return;
|
|
27642
|
+
const spec = checkToEdgeCountSpec(check);
|
|
27643
|
+
const key = edgeCountSpecKey(spec);
|
|
27644
|
+
if (!out.some((s) => edgeCountSpecKey(s) === key)) out.push(spec);
|
|
27645
|
+
}
|
|
27646
|
+
var UPG_EDGE_COUNT_SPECS = (() => {
|
|
27647
|
+
const out = [];
|
|
27648
|
+
for (const ap of UPG_ANTI_PATTERNS) {
|
|
27649
|
+
if (ap.structured_condition) walkForEdgeCountSpecs(ap.structured_condition, out);
|
|
27650
|
+
}
|
|
27651
|
+
return out;
|
|
27652
|
+
})();
|
|
27361
27653
|
function getAntiPatternById(id) {
|
|
27362
27654
|
return UPG_ANTI_PATTERNS.find((ap) => ap.id === id);
|
|
27363
27655
|
}
|
|
@@ -27434,7 +27726,11 @@ function evaluateAntiPatterns(inputs, options) {
|
|
|
27434
27726
|
const concern = concernFor(ap.id);
|
|
27435
27727
|
if (!concernEvaluatedFor(inputs.memberKind, concern)) continue;
|
|
27436
27728
|
if (evaluateCondition(ap.structured_condition, inputs)) {
|
|
27437
|
-
|
|
27729
|
+
const ids = /* @__PURE__ */ new Set();
|
|
27730
|
+
collectAttribution(ap.structured_condition, inputs, ids);
|
|
27731
|
+
const violation = buildViolation(ap, concern);
|
|
27732
|
+
if (ids.size > 0) violation.target_node_ids = [...ids].sort();
|
|
27733
|
+
fires.push(violation);
|
|
27438
27734
|
}
|
|
27439
27735
|
}
|
|
27440
27736
|
fires.sort((a, b) => {
|
|
@@ -27444,6 +27740,25 @@ function evaluateAntiPatterns(inputs, options) {
|
|
|
27444
27740
|
});
|
|
27445
27741
|
return fires;
|
|
27446
27742
|
}
|
|
27743
|
+
function collectAttribution(cond, inputs, out) {
|
|
27744
|
+
if ("operator" in cond) {
|
|
27745
|
+
for (const child of cond.checks) {
|
|
27746
|
+
if (cond.operator === "or" && !evaluateCondition(child, inputs)) continue;
|
|
27747
|
+
collectAttribution(child, inputs, out);
|
|
27748
|
+
}
|
|
27749
|
+
return;
|
|
27750
|
+
}
|
|
27751
|
+
const check = cond.check;
|
|
27752
|
+
if (check.type === "edge_count_vs_property") {
|
|
27753
|
+
const key = edgeCountSpecKey(checkToEdgeCountSpec(check));
|
|
27754
|
+
for (const id of inputs.nodesByEdgeCountSpec?.[key] ?? []) out.add(id);
|
|
27755
|
+
return;
|
|
27756
|
+
}
|
|
27757
|
+
if (check.type === "entity_count" && check.filter) {
|
|
27758
|
+
const key = entityFilterKey(check.entity_type, check.filter);
|
|
27759
|
+
for (const id of inputs.nodesByEntityFilter?.[key] ?? []) out.add(id);
|
|
27760
|
+
}
|
|
27761
|
+
}
|
|
27447
27762
|
function evaluateCondition(cond, inputs) {
|
|
27448
27763
|
if ("check" in cond) {
|
|
27449
27764
|
return evaluateLeaf(cond.check, inputs);
|
|
@@ -27463,6 +27778,8 @@ function evaluateLeaf(check, inputs) {
|
|
|
27463
27778
|
switch (check.type) {
|
|
27464
27779
|
case "entity_count":
|
|
27465
27780
|
return evaluateEntityCount(check, inputs);
|
|
27781
|
+
case "edge_count_vs_property":
|
|
27782
|
+
return evaluateEdgeCountVsProperty(check, inputs);
|
|
27466
27783
|
case "relationship":
|
|
27467
27784
|
return evaluateRelationship(check, inputs);
|
|
27468
27785
|
case "benchmark":
|
|
@@ -27505,7 +27822,13 @@ function compareNumber(value, comparison, threshold) {
|
|
|
27505
27822
|
function evaluateEntityCount(check, inputs) {
|
|
27506
27823
|
const filter = check.filter;
|
|
27507
27824
|
let count = 0;
|
|
27508
|
-
if (filter && typeof filter.property === "string" && typeof filter.present === "boolean") {
|
|
27825
|
+
if (filter && typeof filter.property === "string" && typeof filter.present === "boolean" && typeof filter.except_property === "string" && typeof filter.except_value === "string") {
|
|
27826
|
+
const key = presenceExceptKey(filter.property, filter.except_property, filter.except_value);
|
|
27827
|
+
const exempt = inputs.countsByTypeAndProperty?.[check.entity_type]?.[filter.except_property]?.[filter.except_value] ?? 0;
|
|
27828
|
+
const eligible = Math.max(0, (inputs.countsByType[check.entity_type] ?? 0) - exempt);
|
|
27829
|
+
const withValue = inputs.countsByTypeAndPropertyPresenceExcept?.[check.entity_type]?.[key] ?? 0;
|
|
27830
|
+
count = filter.present ? Math.min(withValue, eligible) : Math.max(0, eligible - withValue);
|
|
27831
|
+
} else if (filter && typeof filter.property === "string" && typeof filter.present === "boolean") {
|
|
27509
27832
|
const withValue = inputs.countsByTypeAndPropertyPresence?.[check.entity_type]?.[filter.property] ?? 0;
|
|
27510
27833
|
count = filter.present ? withValue : Math.max(0, (inputs.countsByType[check.entity_type] ?? 0) - withValue);
|
|
27511
27834
|
} else if (filter && typeof filter.property === "string" && typeof filter.value === "string") {
|
|
@@ -27517,6 +27840,18 @@ function evaluateEntityCount(check, inputs) {
|
|
|
27517
27840
|
}
|
|
27518
27841
|
return compareNumber(count, check.comparison, check.threshold);
|
|
27519
27842
|
}
|
|
27843
|
+
function evaluateEdgeCountVsProperty(check, inputs) {
|
|
27844
|
+
const key = edgeCountSpecKey(checkToEdgeCountSpec(check));
|
|
27845
|
+
const matched = inputs.nodesByEdgeCountSpec?.[key];
|
|
27846
|
+
if (matched === void 0) {
|
|
27847
|
+
return compareNumber(
|
|
27848
|
+
inputs.countsByType[check.entity_type] ?? 0,
|
|
27849
|
+
check.comparison,
|
|
27850
|
+
check.threshold
|
|
27851
|
+
);
|
|
27852
|
+
}
|
|
27853
|
+
return compareNumber(matched.length, check.comparison, check.threshold);
|
|
27854
|
+
}
|
|
27520
27855
|
function relationshipKey(source_type, edge_type, target_type) {
|
|
27521
27856
|
return `${source_type}|${edge_type}|${target_type}`;
|
|
27522
27857
|
}
|
|
@@ -27599,7 +27934,7 @@ function collectTargetEntities(cond) {
|
|
|
27599
27934
|
function walk(c) {
|
|
27600
27935
|
if ("check" in c) {
|
|
27601
27936
|
const leaf = c.check;
|
|
27602
|
-
if (leaf.type === "entity_count" || leaf.type === "benchmark") {
|
|
27937
|
+
if (leaf.type === "entity_count" || leaf.type === "benchmark" || leaf.type === "edge_count_vs_property") {
|
|
27603
27938
|
types.add(leaf.entity_type);
|
|
27604
27939
|
} else if (leaf.type === "relationship") {
|
|
27605
27940
|
types.add(leaf.source_type);
|
|
@@ -30626,7 +30961,7 @@ function checkHeaderSealText(text) {
|
|
|
30626
30961
|
}
|
|
30627
30962
|
|
|
30628
30963
|
// src/index.ts
|
|
30629
|
-
var UPG_VERSION = "0.
|
|
30964
|
+
var UPG_VERSION = "0.29.0";
|
|
30630
30965
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
30631
30966
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
30632
30967
|
var UPG_TYPES = getTypes();
|
|
@@ -30741,10 +31076,12 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30741
31076
|
UPG_DOMAIN_RINGS,
|
|
30742
31077
|
UPG_EDGE_CATALOG,
|
|
30743
31078
|
UPG_EDGE_COUNT,
|
|
31079
|
+
UPG_EDGE_COUNT_SPECS,
|
|
30744
31080
|
UPG_EDGE_MIGRATIONS,
|
|
30745
31081
|
UPG_EDGE_PAIR_MAP,
|
|
30746
31082
|
UPG_EDGE_TYPES,
|
|
30747
31083
|
UPG_ENTITY_COUNT,
|
|
31084
|
+
UPG_ENTITY_FILTER_SPECS,
|
|
30748
31085
|
UPG_ENTITY_META,
|
|
30749
31086
|
UPG_ENTITY_META_BY_ID,
|
|
30750
31087
|
UPG_ENTITY_META_BY_NAME,
|
|
@@ -30765,6 +31102,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30765
31102
|
UPG_POLYMORPHIC_EDGE_KEYS,
|
|
30766
31103
|
UPG_PORTFOLIO_KINDS,
|
|
30767
31104
|
UPG_PORTFOLIO_SHARED_TYPES,
|
|
31105
|
+
UPG_PRESENCE_EXCEPT_SPECS,
|
|
30768
31106
|
UPG_PRODUCT_STAGES,
|
|
30769
31107
|
UPG_PRODUCT_STAGE_COERCION_MAP,
|
|
30770
31108
|
UPG_PROPERTY_MIGRATIONS,
|
|
@@ -30798,8 +31136,11 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30798
31136
|
canBeChildOf,
|
|
30799
31137
|
checkHeaderSeal,
|
|
30800
31138
|
checkHeaderSealText,
|
|
31139
|
+
checkToEdgeCountSpec,
|
|
31140
|
+
classifyEntityFilter,
|
|
30801
31141
|
coerceProductStage,
|
|
30802
31142
|
collectSlugsForType,
|
|
31143
|
+
collectTargetEntities,
|
|
30803
31144
|
compareVersions,
|
|
30804
31145
|
computeBodyChecksum,
|
|
30805
31146
|
concernEvaluatedFor,
|
|
@@ -30811,6 +31152,8 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30811
31152
|
deriveSingleCounts,
|
|
30812
31153
|
describeTreePattern,
|
|
30813
31154
|
edgeCarriesProperties,
|
|
31155
|
+
edgeCountSpecKey,
|
|
31156
|
+
entityFilterKey,
|
|
30814
31157
|
evaluateAntiPatterns,
|
|
30815
31158
|
findInvalidStatusMigrationTargets,
|
|
30816
31159
|
formatUpgText,
|
|
@@ -30901,6 +31244,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
30901
31244
|
normalizeDocument,
|
|
30902
31245
|
parseUpg,
|
|
30903
31246
|
pickCanonicalEdge,
|
|
31247
|
+
presenceExceptKey,
|
|
30904
31248
|
resolveAllEdges,
|
|
30905
31249
|
resolveContainmentEdge,
|
|
30906
31250
|
resolveEntityType,
|