@unified-product-graph/core 0.23.1 → 0.24.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/dist/index.js CHANGED
@@ -1438,8 +1438,14 @@ var UPG_EDGE_CATALOG = {
1438
1438
  // feature-level edges (same verbs/classification), so `pickCanonicalEdge` /
1439
1439
  // `resolveContainmentEdge('epic', 'task'|'bug')` and the adapter import path
1440
1440
  // resolve them with no further wiring.
1441
- epic_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "epic", target_type: "task" },
1442
- epic_affected_by_bug: { forward_verb: "affected_by", reverse_verb: "affects", classification: "hierarchy", source_type: "epic", target_type: "bug" },
1441
+ // cross_product_eligible (feedback 9a10be30): an epic is a cross-team planning
1442
+ // unit — its implementing task or affecting bug may live in a different
1443
+ // product's engineering substrate (e.g. a consumer product's epic whose
1444
+ // backend work is tracked in a separate platform/infra graph). Dual-registered
1445
+ // like the objective↔dependency edges: valid within a product AND across
1446
+ // products (endpoints referenced by qualified id).
1447
+ epic_decomposes_into_task: { forward_verb: "decomposes_into", reverse_verb: "implements", classification: "hierarchy", source_type: "epic", target_type: "task", cross_product_eligible: true },
1448
+ epic_affected_by_bug: { forward_verb: "affected_by", reverse_verb: "affects", classification: "hierarchy", source_type: "epic", target_type: "bug", cross_product_eligible: true },
1443
1449
  task_has_subtask: { forward_verb: "has_subtask", reverse_verb: "is_subtask_of", classification: "hierarchy", source_type: "task", target_type: "task" },
1444
1450
  // ── Planning cadence (0.20.0) ────────────────────────────────────────────────
1445
1451
  // The cadence axis. E1 self-nests the interval (a program-increment contains
@@ -3247,7 +3253,105 @@ var UPG_EDGE_CATALOG = {
3247
3253
  // product's feature or capability, authored in a different graph within the
3248
3254
  // portfolio, fits the 0.17.3 locked predicate.
3249
3255
  objective_defers_feature: { forward_verb: "defers", reverse_verb: "deferred_by", classification: "cross-domain", source_type: "objective", target_type: "feature", carries_properties: true, property_schema: DEFER_EDGE_PROPERTY_SCHEMA, cross_product_eligible: true, deliberate_only: true },
3250
- objective_defers_capability: { forward_verb: "defers", reverse_verb: "deferred_by", classification: "semantic", source_type: "objective", target_type: "capability", carries_properties: true, property_schema: DEFER_EDGE_PROPERTY_SCHEMA, cross_product_eligible: true, deliberate_only: true }
3256
+ objective_defers_capability: { forward_verb: "defers", reverse_verb: "deferred_by", classification: "semantic", source_type: "objective", target_type: "capability", carries_properties: true, property_schema: DEFER_EDGE_PROPERTY_SCHEMA, cross_product_eligible: true, deliberate_only: true },
3257
+ // ── Enterprise GTM coordination batch (0.24.0, Track 1 Wave A) ───────────────
3258
+ // The catalog modelled each go-to-market department's nouns but almost none of
3259
+ // the handoffs between them, and the enterprise motion IS the handoffs (audit
3260
+ // `2026-07-16-enterprise-gtm-coverage-audit.md`, §3). Sixteen additive edges
3261
+ // wire the buying committee onto the deal, give the enablement lattice a demand
3262
+ // side, close the pre-sale win/loss LEARN loop, make post-sale account-scoped,
3263
+ // and complete the audience-projection lattice. None are hierarchy — every one
3264
+ // is a lateral coordination link — so UPG_VALID_CHILDREN is untouched. Verbs
3265
+ // are precedent-matched (cited per row); classification per the standard
3266
+ // taxonomy. Two edges are portfolio-crossing by construction (a deal in a
3267
+ // field-ops graph blocked by a feature in a product graph; a win/loss study in
3268
+ // a research graph analysing a deal in another) → cross_product_eligible at
3269
+ // mint, one line rather than a later migration (audit §7-addendum decision 6).
3270
+ // Buying committee (F2). A contact was reachable only via account_contains_contact;
3271
+ // it could not attach to the deal it influences. This is the substrate of every
3272
+ // qualification framework (MEDDICC/SPICED) and of multi-threading. Semantic, not
3273
+ // hierarchy: the contact is parented under its account (account_contains_contact),
3274
+ // so a deal references it laterally, not by containment; and semantic, not
3275
+ // cross-domain, because deal and contact are both in the sales domain (T1.7
3276
+ // guardrail: a within-domain edge is not cross-domain). @example deal "Northwind
3277
+ // platform expansion" involves contact "VP Engineering" (buying_role: 'economic_buyer').
3278
+ deal_involves_contact: { forward_verb: "involves", reverse_verb: "involved_in", classification: "semantic", source_type: "deal", target_type: "contact" },
3279
+ // Enablement demand side (F3). GTM's objection→rebuttal→proof_point chain had
3280
+ // supply but nothing consuming it in a deal context. Mirrors the positioning
3281
+ // precedent (positioning_challenged_by_objection). @example deal "Northwind
3282
+ // platform expansion" challenged_by objection "no SOC 2 report yet".
3283
+ deal_challenged_by_objection: { forward_verb: "challenged_by", reverse_verb: "challenges", classification: "cross-domain", source_type: "deal", target_type: "objection" },
3284
+ // Enablement deployed in context (F3). Which battlecard was armed on which deal.
3285
+ // Mirrors gtm_strategy_arms_with_competitive_battle_card. @example deal armed_with
3286
+ // competitive_battle_card "vs. Meridian Cloud".
3287
+ deal_armed_with_competitive_battle_card: { forward_verb: "armed_with", reverse_verb: "arms", classification: "cross-domain", source_type: "deal", target_type: "competitive_battle_card" },
3288
+ // Loss cause (F3). Gives competitive_battle_card.win_rate its derivation path —
3289
+ // nothing in the graph could previously compute it. Causal (a directional
3290
+ // outcome), mirroring lead_becomes_account. @example deal "Sterling Data Systems
3291
+ // renewal" lost_to competitor "Meridian Cloud".
3292
+ deal_lost_to_competitor: { forward_verb: "lost_to", reverse_verb: "won", classification: "causal", source_type: "deal", target_type: "competitor" },
3293
+ // Close mechanism (F4). MSA/SOW/order form reachable from the deal; contract
3294
+ // previously bound only legal_entity and partnership. Mirrors
3295
+ // deal_quoted_via_quote_document. @example deal closed_via contract "Northwind MSA".
3296
+ deal_closed_via_contract: { forward_verb: "closed_via", reverse_verb: "closes", classification: "cross-domain", source_type: "deal", target_type: "contract" },
3297
+ // Pipeline blocker (F5). "Which roadmap items unblock how much pipeline"; plugs
3298
+ // into change_blast_radius. Mirrors epic_affected_by_bug. cross_product_eligible:
3299
+ // the deal lives in the field-ops graph, the blocking feature in the product
3300
+ // graph. @example deal blocked_by feature "SAML SSO".
3301
+ deal_blocked_by_feature: { forward_verb: "blocked_by", reverse_verb: "blocks", classification: "cross-domain", source_type: "deal", target_type: "feature", cross_product_eligible: true },
3302
+ // Win/loss LEARN loop (F3). Win/loss analysis is a research genre, not a new
3303
+ // type — a research_study analyses the deal. Closes the pre-sale learning loop
3304
+ // (the post-sale one already existed via support_ticket_reveals_need). Mirrors
3305
+ // proof_point_derived_from_insight's provenance shape. cross_product_eligible:
3306
+ // the study may live in a research graph distinct from the deal's field-ops
3307
+ // graph. @example research_study "Q3 competitive win/loss" analyzes deal
3308
+ // "Sterling Data Systems renewal".
3309
+ research_study_analyzes_deal: { forward_verb: "analyzes", reverse_verb: "analyzed_by", classification: "cross-domain", source_type: "research_study", target_type: "deal", cross_product_eligible: true },
3310
+ // Account-scoped sensing (F5). Nearly every CS edge hung off product; only one
3311
+ // scoped anything to an account. Mirrors product_supports_via_support_ticket.
3312
+ // @example account "Northwind Logistics" raises support_ticket "onboarding SSO
3313
+ // blocker".
3314
+ account_raises_support_ticket: { forward_verb: "raises", reverse_verb: "raised_by", classification: "cross-domain", source_type: "account", target_type: "support_ticket" },
3315
+ // Account health (F5). Mirrors product_health_scored_via_customer_health_score.
3316
+ // @example account health_scored_via customer_health_score "Northwind Q3 health".
3317
+ account_health_scored_via_customer_health_score: { forward_verb: "health_scored_via", reverse_verb: "scores", classification: "cross-domain", source_type: "account", target_type: "customer_health_score" },
3318
+ // Account churn (F5). Churn lands on the account, not just the product. Mirrors
3319
+ // product_loses_because_churn_reason. @example account lost_because churn_reason
3320
+ // "switched to incumbent suite".
3321
+ account_lost_because_churn_reason: { forward_verb: "lost_because", reverse_verb: "causes_churn_for", classification: "cross-domain", source_type: "account", target_type: "churn_reason" },
3322
+ // Onboarding / implementation (F5). Reuses the Program Management machinery
3323
+ // (project/milestone/deliverable/status_report) for enterprise onboarding.
3324
+ // Mirrors the operate-a-thing-via-a-plan shape of product_operated_via_playbook.
3325
+ // @example account implements_via project "Northwind rollout".
3326
+ account_implements_via_project: { forward_verb: "implements_via", reverse_verb: "implements", classification: "cross-domain", source_type: "account", target_type: "project" },
3327
+ // Renewal object-path (F5). A renewal is a deal of deal_type 'renewal'; this
3328
+ // completes the lead→account→deal→subscription→deal cycle. Causal (a directional
3329
+ // renewal event), mirroring quote_document_advances_deal. @example subscription
3330
+ // "Northwind annual" renews_via deal "Northwind FY27 renewal".
3331
+ subscription_renews_via_deal: { forward_verb: "renews_via", reverse_verb: "renews", classification: "causal", source_type: "subscription", target_type: "deal" },
3332
+ // Enterprise early access (F5, rider R1). An account participates in a beta
3333
+ // program. Mirrors product_runs_beta_program's noun. @example account
3334
+ // participates_in beta_program "SSO private beta".
3335
+ account_participates_in_beta_program: { forward_verb: "participates_in", reverse_verb: "has_participant", classification: "cross-domain", source_type: "account", target_type: "beta_program" },
3336
+ // Security gate (F4, rider R2). The security questionnaire/review as a deal gate,
3337
+ // reusing the Security noun rather than minting security_questionnaire. Mirrors
3338
+ // service_level_agreement_covers_account's account↔security cross-domain shape.
3339
+ // @example deal gated_by security_review "Northwind vendor security assessment".
3340
+ deal_gated_by_security_review: { forward_verb: "gated_by", reverse_verb: "gates", classification: "cross-domain", source_type: "deal", target_type: "security_review" },
3341
+ // Audience-projection subject axis (Ruling 4, A15). messaging's only subject edge
3342
+ // was positioning_communicated_via_messaging — messaging could not be ABOUT a
3343
+ // feature, so audience-scoped feature projections were unexpressable at the root.
3344
+ // Mirrors the positioning precedent. The rendered projection is NOT stored (views
3345
+ // philosophy); this edge stores the atom the lens queries. @example feature "SAML
3346
+ // SSO" communicated_via messaging "SSO beta enablement one-pager".
3347
+ feature_communicated_via_messaging: { forward_verb: "communicated_via", reverse_verb: "communicates", classification: "cross-domain", source_type: "feature", target_type: "messaging" },
3348
+ // Launch coordination seam (Ruling 4, A16). launch had edges to
3349
+ // release/feature/channel/campaign/metric/messaging but none to Program
3350
+ // Management — the GTM readiness checklist (who builds the demo env, trains the
3351
+ // SEs, by when) had no home. Reuses project/milestone/deliverable, the same move
3352
+ // as account_implements_via_project. @example launch "SSO GA" coordinated_via
3353
+ // project "SSO launch readiness".
3354
+ launch_coordinated_via_project: { forward_verb: "coordinated_via", reverse_verb: "coordinates", classification: "cross-domain", source_type: "launch", target_type: "project" }
3251
3355
  };
3252
3356
  var UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES = Object.keys(UPG_EDGE_CATALOG).filter(
3253
3357
  (k) => UPG_EDGE_CATALOG[k].cross_product_eligible === true
@@ -6555,6 +6659,8 @@ var PROPERTY_SCALE_MAP = {
6555
6659
  // market/risk (UPG-579 Option B)
6556
6660
  probability: "confidence_5",
6557
6661
  // sales/forecast (UPG-579 Option B)
6662
+ qualification_score: "confidence_5",
6663
+ // sales/deal — how confident the qualification is (0.24.0)
6558
6664
  // ── Effort ───────────────────────────────────────────────────────────────
6559
6665
  effort: "effort_5",
6560
6666
  // discovery
@@ -10049,7 +10155,10 @@ var UPG_PROPERTY_SCHEMA = {
10049
10155
  account: {
10050
10156
  account_type: { type: "string", enum: ["prospect", "customer", "partner", "churned"], description: "Relationship status of this account" },
10051
10157
  industry: { type: "string", description: "Industry vertical the account operates in" },
10052
- employee_count: { type: "number", description: "Number of employees at the account", modifier: "snapshot" }
10158
+ employee_count: { type: "number", description: "Number of employees at the account", modifier: "snapshot" },
10159
+ segment: { type: "string", enum: ["smb", "mid_market", "enterprise", "strategic"], description: "Go-to-market tier this account is served at. A segmentation *decision* (how the field org treats the account), distinct from `employee_count` (a raw fact) and aligned with but not identical to the ideal customer profile's `company_size` buckets. `strategic` is the tier that escalates to a dedicated account-plan graph (Tier 3 client model)." },
10160
+ annual_contract_value: { type: "number", description: "Annual contract value in the account's billing currency. The single most common enterprise account-tiering input; a snapshot that changes on expansion or renewal.", modifier: "snapshot" },
10161
+ region: { type: "string", description: 'Geographic region the account is managed in (e.g. "EMEA", "NA-West"). Free-form: territory taxonomies vary by org and are modelled structurally via the `territory` entity when they need to be queryable.' }
10053
10162
  },
10054
10163
  // AcquisitionChannelProperties: AcquisitionChannel entity.
10055
10164
  acquisition_channel: {
@@ -10638,7 +10747,8 @@ var UPG_PROPERTY_SCHEMA = {
10638
10747
  // ContactProperties: Contact.
10639
10748
  contact: {
10640
10749
  contact_role: { type: "string", description: "Job title or role within the account" },
10641
- is_decision_maker: { type: "boolean", description: "Whether this person has purchasing authority" }
10750
+ is_decision_maker: { type: "boolean", description: "Whether this person has purchasing authority" },
10751
+ buying_role: { type: "string", enum: ["champion", "economic_buyer", "technical_evaluator", "end_user", "detractor", "influencer", "procurement", "legal", "security"], description: "Role this contact plays in the buying committee (the decision-making unit). The substrate of enterprise multi-threading and of qualification frameworks (MEDDICC/SPICED): a deal with no `champion` and no `economic_buyer` mapped is single-threaded and at risk. `is_decision_maker` becomes largely derivable (`buying_role = economic_buyer`) but is kept for back-compat. @example 'economic_buyer'" }
10642
10752
  },
10643
10753
  // ContentCalendarProperties: Content calendar.
10644
10754
  content_calendar: {
@@ -10834,7 +10944,24 @@ var UPG_PROPERTY_SCHEMA = {
10834
10944
  deal: {
10835
10945
  deal_value: { type: "number", description: "Monetary value of the deal" },
10836
10946
  close_date: { type: "string", description: "Expected close date (ISO format)" },
10837
- probability: { type: "number", description: "Likelihood of closing (0-100%)" }
10947
+ probability: { type: "number", description: "Likelihood of closing (0-100%)" },
10948
+ deal_outcome: { type: "string", enum: ["won", "lost", "no_decision"], description: "Terminal result of the deal. An Event-axis outcome (the verdict on a one-time event), NOT a lifecycle phase, so it is `deal_outcome` and not `deal_status` per the status-convention Rule 3. Lifecycle (open/won/lost as phases) belongs on the base `status` slot; this records the win/loss verdict a closed deal carries and gives `deal_lost_to_competitor` / win-loss study derivations their anchor. @example 'won'" },
10949
+ deal_type: { type: "string", enum: ["new_business", "expansion", "renewal"], description: "Motion this deal belongs to. Mirrors `PipelineSales.pipeline_type` at the deal grain; `renewal` is the value `subscription_renews_via_deal` points at. @example 'expansion'" },
10950
+ next_step: { type: "string", description: "The single most-used CRM field: the next concrete action to move the deal. Free text on purpose (a coordination note, not a structured task). @example 'Send security questionnaire to procurement'" },
10951
+ next_step_date: { type: "string", description: "When the `next_step` is due (ISO format)." },
10952
+ qualification_framework: { type: "string", enum: ["meddicc", "bant", "spiced", "custom"], description: "Qualification framework this deal is scored against. Names the rubric so the `qualification_score` is interpretable; per-pillar booleans (MEDDICC's Metrics / Economic buyer / Decision criteria / \u2026) are deferred until `custom` recurrence proves the demand. @example 'meddicc'" },
10953
+ qualification_score: {
10954
+ type: "assessment",
10955
+ scale_id: "confidence_5",
10956
+ description: "How well-qualified the deal is, on the canonical `confidence_5` scale (UPGAssessment: numeric value plus a high/medium/low label). Coarse by design; the granular per-pillar breakdown is deferred (see `qualification_framework`).",
10957
+ properties: {
10958
+ value: { type: "number", description: "The numeric value, used for computation." },
10959
+ label: { type: "string", description: "The qualitative label (what the assessor meant)." },
10960
+ scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
10961
+ normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
10962
+ },
10963
+ required: ["value", "label"]
10964
+ }
10838
10965
  },
10839
10966
  // DecisionProperties: Decision record. Strategic, product, engineering, or design.
10840
10967
  decision: {
@@ -11867,7 +11994,7 @@ var UPG_PROPERTY_SCHEMA = {
11867
11994
  },
11868
11995
  // MessagingProperties: Messaging entity. Each instance is a channel/stage variant.
11869
11996
  messaging: {
11870
- channel: { type: "string", enum: ["landing_page", "email", "social", "ad", "pitch", "press", "in_product", "other"], description: "Channel this messaging variant is crafted for" },
11997
+ channel: { type: "string", enum: ["landing_page", "email", "social", "ad", "pitch", "press", "in_product", "enablement", "other"], description: "Channel this messaging variant is crafted for. `enablement` is the internal field-ops channel (SE/PMM/SA enablement one-pagers, battlecards' narrative), distinct from `pitch` (external, customer-facing)." },
11871
11998
  funnel_stage: { type: "string", enum: ["awareness", "consideration", "conversion", "retention"], description: "Marketing funnel stage this variant targets" },
11872
11999
  headline: { type: "string", description: "Primary headline or hook" },
11873
12000
  body: { type: "string", description: "Full message body copy" },
@@ -23277,6 +23404,12 @@ var GTM_GUIDE = {
23277
23404
  description: "Strategy defines positioning, positioning spawns messaging variants, messaging addresses objections with rebuttals backed by proof points",
23278
23405
  entity_types: ["positioning", "messaging", "objection", "rebuttal", "proof_point"],
23279
23406
  edge_chain: ["gtm_strategy_positions_via_positioning", "positioning_communicated_via_messaging", "positioning_challenged_by_objection", "objection_countered_by_rebuttal", "rebuttal_evidenced_by_proof_point"]
23407
+ },
23408
+ {
23409
+ name: "Audience Projection Lattice",
23410
+ description: "One feature approaching launch needs many faces: what the internal field org (SE/PMM/SA) needs to know differs from what an enterprise champion needs at beta versus GA. Model the atoms, not the rendered pages. A feature is communicated_via messaging variants; each variant targets a persona (an internal field-ops persona or a customer persona) and is used_in a staged launch (beta, then GA); the launch is coordinated_via a project (the readiness checklist: demo env, SE training, by when). The projection itself is a query over these atoms, rendered by the product, never a stored copy that drifts.",
23411
+ entity_types: ["feature", "messaging", "persona", "launch", "project"],
23412
+ edge_chain: ["feature_communicated_via_messaging", "messaging_targets_persona", "launch_ships_feature", "launch_coordinated_via_project"]
23280
23413
  }
23281
23414
  ],
23282
23415
  required_bridges: [
@@ -23322,17 +23455,33 @@ var SALES_GUIDE = {
23322
23455
  description: "Pipelines qualify leads, leads become accounts, accounts negotiate deals that progress through pipeline stages and convert to subscriptions",
23323
23456
  entity_types: ["pipeline_sales", "lead", "account", "deal", "pipeline_stage", "subscription"],
23324
23457
  edge_chain: ["pipeline_sales_qualifies_lead", "lead_becomes_account", "account_negotiates_deal", "deal_at_pipeline_stage", "pipeline_sales_converts_to_subscription"]
23458
+ },
23459
+ {
23460
+ name: "Enterprise Buying Committee",
23461
+ description: "An enterprise deal is a months-long coordination effort, not a forecast row. Attach the decision-making unit directly to the deal (each contact carries a buying_role: champion, economic buyer, technical evaluator, procurement, detractor), then wire the gauntlet the deal must clear: objections it faces, the security review that gates it, and the contract it closes via. A deal with no champion and no economic buyer mapped is single-threaded and at risk.",
23462
+ entity_types: ["deal", "contact", "objection", "security_review", "contract"],
23463
+ edge_chain: ["account_negotiates_deal", "deal_involves_contact", "deal_challenged_by_objection", "deal_gated_by_security_review", "deal_closed_via_contract"]
23464
+ },
23465
+ {
23466
+ name: "Win/Loss Learning Loop",
23467
+ description: "Close the pre-sale learning loop the way the post-sale one already closes (support_ticket reveals need). A deal is armed with a competitive battlecard; when it is lost to a competitor, a win/loss research_study analyses it. This gives competitive_battle_card.win_rate a derivation path (nothing could compute it before) and turns lost deals into structured competitive intelligence rather than a note in a closed record.",
23468
+ entity_types: ["deal", "competitive_battle_card", "competitor", "research_study"],
23469
+ edge_chain: ["deal_armed_with_competitive_battle_card", "deal_lost_to_competitor", "research_study_analyzes_deal"]
23325
23470
  }
23326
23471
  ],
23327
23472
  required_bridges: [
23328
23473
  { edge_type: "lead_sourced_from_acquisition_channel", target_domain: "growth", when: "Track where leads come from for attribution" },
23329
23474
  { edge_type: "subscription_subscribes_to_pricing_tier", target_domain: "pricing", when: "Subscriptions should link to the tier purchased" },
23330
- { edge_type: "account_partners_via_partnership", target_domain: "business_model", when: "Partner accounts should link to the partnership entity" }
23475
+ { edge_type: "account_partners_via_partnership", target_domain: "business_model", when: "Partner accounts should link to the partnership entity" },
23476
+ { edge_type: "deal_blocked_by_feature", target_domain: "product_spec", when: 'When a roadmap gap holds up pipeline, link the deal to the blocking feature so "which features unblock how much revenue" is queryable' },
23477
+ { edge_type: "account_implements_via_project", target_domain: "program_mgmt", when: "Post-sale onboarding/implementation reuses the program-management machinery (project/milestone/deliverable), scoped to the account" }
23331
23478
  ],
23332
23479
  anti_patterns: [
23333
23480
  { description: "Deals without pipeline stages: every deal needs a current position in the pipeline" },
23334
23481
  { description: "Leads without source attribution. Attribution drives channel optimisation." },
23335
- { description: "Forecasts without probability: weight deals by likelihood to close" }
23482
+ { description: "Forecasts without probability: weight deals by likelihood to close" },
23483
+ { name: "Single-threaded enterprise deal", description: "An enterprise deal with no contacts attached (deal_involves_contact) or no champion/economic_buyer among their buying_role values is single-threaded: the whole relationship rests on one unmapped person. Attach the buying committee and map at least the champion and economic buyer.", affected_entity: "deal", remediation: "Create deal_involves_contact edges to the committee and set each contact's buying_role." },
23484
+ { name: "Won/lost recorded only as a phase", description: "The deal outcome (won/lost/no_decision) is an Event-axis fact and belongs on deal_outcome, not conflated with the lifecycle status. A lost deal with no deal_lost_to_competitor edge also strands the competitive signal that competitive_battle_card.win_rate depends on.", affected_entity: "deal", remediation: "Set deal_outcome, and on a loss create deal_lost_to_competitor plus a win/loss research_study via research_study_analyzes_deal." }
23336
23485
  ]
23337
23486
  };
23338
23487
  var MARKETING_GUIDE = {
@@ -29721,7 +29870,7 @@ function isCanonical(text) {
29721
29870
  }
29722
29871
 
29723
29872
  // src/index.ts
29724
- var UPG_VERSION = "0.23.1";
29873
+ var UPG_VERSION = "0.24.0";
29725
29874
  var UPG_FORMAT_VERSION = "0.4.0";
29726
29875
  var MARKDOWN_FORMAT_VERSION = "0.1";
29727
29876
  var UPG_TYPES = getTypes();