@unified-product-graph/core 0.34.0 → 0.35.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
@@ -1199,6 +1199,19 @@ var UPG_EDGE_CATALOG = {
1199
1199
  product_contains_research_study: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "product", target_type: "research_study" },
1200
1200
  research_study_enrolls_participant: { forward_verb: "enrolls", reverse_verb: "enrolled_in", classification: "hierarchy", source_type: "research_study", target_type: "participant" },
1201
1201
  research_study_captures_observation: { forward_verb: "captures", reverse_verb: "captured_in", classification: "hierarchy", source_type: "research_study", target_type: "observation" },
1202
+ // (0.35.0) Provenance for a verbatim quote: which study produced it. The
1203
+ // quote card wanted an outbound `quote_from_research_study`; canon answers it
1204
+ // study-first, matching the whole `research_study_*` family (enrolls ·
1205
+ // captures · clusters_into · produces · investigates · follows · collects),
1206
+ // so the quote renders it INBOUND on its connections rail. The `captures`
1207
+ // verb is deliberately reused from the observation twin above: the F3
1208
+ // duplicate gate is on the (source, target) pair, which is new, and a study
1209
+ // captures raw material by one verb regardless of which raw shape it is.
1210
+ // Companion: `UPG_VALID_CHILDREN.research_study` gains 'quote'. A quote may
1211
+ // now hang from observation (raw-capture parent), insight (synthesis parent)
1212
+ // or research_study (provenance parent) — multi-parent grammar is explicitly
1213
+ // sanctioned, and per-INSTANCE parentage stays single via `parent_id`.
1214
+ research_study_captures_quote: { forward_verb: "captures", reverse_verb: "captured_in", classification: "hierarchy", source_type: "research_study", target_type: "quote" },
1202
1215
  research_study_clusters_into_affinity_cluster: { forward_verb: "clusters_into", reverse_verb: "clustered_from", classification: "hierarchy", source_type: "research_study", target_type: "affinity_cluster" },
1203
1216
  research_study_produces_insight: { forward_verb: "produces", reverse_verb: "produced_by", classification: "hierarchy", source_type: "research_study", target_type: "insight" },
1204
1217
  research_study_investigates_research_question: { forward_verb: "investigates", reverse_verb: "investigated_by", classification: "hierarchy", source_type: "research_study", target_type: "research_question" },
@@ -1480,6 +1493,22 @@ var UPG_EDGE_CATALOG = {
1480
1493
  // the work into `task`; v0.7.0/UPG-571 re-canonicalised the statement
1481
1494
  // story_statement → user_story; see UPG_EDGE_MIGRATIONS['0.7.0'].)
1482
1495
  epic_specified_by_user_story: { forward_verb: "specified_by", reverse_verb: "specifies", classification: "hierarchy", source_type: "epic", target_type: "user_story" },
1496
+ // (0.35.0) The feature-level rung of the same ladder. `epic` is OPTIONAL in
1497
+ // UPG — `feature_decomposed_into_epic` is not mandatory — so a feature that
1498
+ // skips the epic level had no way to reach the stories that specify it, and
1499
+ // `user_story` had exactly one declared parent. Verb pair is cloned verbatim
1500
+ // from the epic rung above so the two rungs read identically; it is NOT
1501
+ // `groups`, which would invent a second verb for one relationship.
1502
+ //
1503
+ // This is also the reason `feature_contains_acceptance_criterion` is NOT
1504
+ // here: with this rung the path is feature → user_story → acceptance_
1505
+ // criterion, and a direct containment edge would be the "X_contains_Y verb
1506
+ // that duplicates the parent link" ARCHITECTURE.md forbids.
1507
+ //
1508
+ // Companion: `UPG_VALID_CHILDREN.feature` gains 'user_story'. Per-instance
1509
+ // parentage stays single (`parent_id`), so the dual declared parent is a
1510
+ // grammar widening, not a dual-parent instance.
1511
+ feature_specified_by_user_story: { forward_verb: "specified_by", reverse_verb: "specifies", classification: "hierarchy", source_type: "feature", target_type: "user_story" },
1483
1512
  user_story_verified_by_acceptance_criterion: { forward_verb: "verified_by", reverse_verb: "verifies", classification: "hierarchy", source_type: "user_story", target_type: "acceptance_criterion" },
1484
1513
  task_implements_user_story: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "cross-domain", source_type: "task", target_type: "user_story" },
1485
1514
  feature_affected_by_bug: { forward_verb: "affected_by", reverse_verb: "affects", classification: "hierarchy", source_type: "feature", target_type: "bug" },
@@ -2576,6 +2605,16 @@ var UPG_EDGE_CATALOG = {
2576
2605
  team_decides_decision: { forward_verb: "decides", reverse_verb: "decided_by", classification: "hierarchy", source_type: "team", target_type: "decision" },
2577
2606
  decision_references_decision: { forward_verb: "references", reverse_verb: "referenced_by", classification: "cross-domain", source_type: "decision", target_type: "decision" },
2578
2607
  stakeholder_maps_to_persona: { forward_verb: "maps_to", reverse_verb: "mapped_by", classification: "cross-domain", source_type: "stakeholder", target_type: "persona" },
2608
+ // (0.35.0) The stakeholder-map relation: who cares about which outcome.
2609
+ // Deliberately a verb-only edge and NOT a `kind: 'scale'` property on a
2610
+ // generic `stakeholder_relates_to_node` — UPG edges are payload-free outside
2611
+ // the gated `carries_properties` set, and the MAGNITUDE of the stake is
2612
+ // already `influence` / `interest` on the stakeholder itself. The reverse
2613
+ // reading is what makes the pair right: "outcome matters_to stakeholder".
2614
+ // Sits beside `persona_pursues_outcome` (the persona twin) and
2615
+ // `product_influenced_by_stakeholder`. Retires the manifest's denormalised
2616
+ // `stake_in` string: the subtitle renders the target outcome's title.
2617
+ stakeholder_invested_in_outcome: { forward_verb: "invested_in", reverse_verb: "matters_to", classification: "cross-domain", source_type: "stakeholder", target_type: "outcome" },
2579
2618
  team_okr_aligns_with_objective: { forward_verb: "aligns_with", reverse_verb: "aligned_by", classification: "cross-domain", source_type: "team_okr", target_type: "objective" },
2580
2619
  team_okr_aligns_with_key_result: { forward_verb: "aligns_with", reverse_verb: "aligned_by", classification: "cross-domain", source_type: "team_okr", target_type: "key_result" },
2581
2620
  ceremony_involves_team: { forward_verb: "involves", reverse_verb: "involved_in", classification: "cross-domain", source_type: "ceremony", target_type: "team" },
@@ -2654,6 +2693,27 @@ var UPG_EDGE_CATALOG = {
2654
2693
  compliance_requirement_constrains_feature: { forward_verb: "constrains", reverse_verb: "constrained_by", classification: "cross-domain", source_type: "compliance_requirement", target_type: "feature" },
2655
2694
  compliance_requirement_constrains_decision: { forward_verb: "constrains", reverse_verb: "constrained_by", classification: "cross-domain", source_type: "compliance_requirement", target_type: "decision" },
2656
2695
  risk_manifests_as_technical_debt_item: { forward_verb: "manifests_as", reverse_verb: "manifested_by", classification: "cross-domain", source_type: "risk", target_type: "technical_debt_item" },
2696
+ // ── Risk exposure (0.35.0) ──────────────────────────────────────────────────
2697
+ // Typed source, wildcard target: the 16th registered polymorphic family, and
2698
+ // structurally the "decision-to-anything" construction with `risk` in the
2699
+ // source slot. Both are `cross-domain`, so neither enters UPG_VALID_CHILDREN
2700
+ // and neither can be walked as containment.
2701
+ //
2702
+ // Why wildcard rather than typed-per-target: what a risk puts at stake is
2703
+ // genuinely unbounded (outcome, key_result, release, service, contract,
2704
+ // launch, metric, feature), and so is what mitigates it (decision, feature,
2705
+ // experiment, security_control, compliance_requirement). Enumerating either
2706
+ // side balloons the catalogue across two open sets while the endpoint carries
2707
+ // no structural role — the exact condition ARCHITECTURE.md admits polymorphism
2708
+ // for. The typed `security_control_mitigates_threat` stays as the
2709
+ // security-domain edge; `risk_mitigated_by_node` is the product generalisation.
2710
+ //
2711
+ // These two replace three manifest fields that were properties pretending to
2712
+ // be relations: `scope_entities` (an entity-reference list) is
2713
+ // risk_threatens_node, and `mitigation_actions` (a top-list of action
2714
+ // strings) is risk_mitigated_by_node. The prose `mitigation?: string` stays.
2715
+ risk_threatens_node: { forward_verb: "threatens", reverse_verb: "threatened_by", classification: "cross-domain", source_type: "risk", target_type: "node" },
2716
+ risk_mitigated_by_node: { forward_verb: "mitigated_by", reverse_verb: "mitigates", classification: "cross-domain", source_type: "risk", target_type: "node" },
2657
2717
  data_contract_governs_data_source: { forward_verb: "governs", reverse_verb: "governed_by", classification: "cross-domain", source_type: "data_contract", target_type: "data_source" },
2658
2718
  compliance_framework_requires_security_control: { forward_verb: "requires", reverse_verb: "required_by", classification: "cross-domain", source_type: "compliance_framework", target_type: "security_control" },
2659
2719
  security_audit_validates_compliance_framework: { forward_verb: "validates", reverse_verb: "validated_by", classification: "cross-domain", source_type: "security_audit", target_type: "compliance_framework" },
@@ -3940,7 +4000,12 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
3940
4000
  // Document transclusion (0.34.0): a document embeds any renderable node's live
3941
4001
  // value at a position in its prose. Open by nature rather than by an
3942
4002
  // enumeration nobody wanted to write out.
3943
- "document_transcludes_node"
4003
+ "document_transcludes_node",
4004
+ // Risk exposure (0.35.0): what a risk threatens, and what mitigates it.
4005
+ // Typed source, wildcard target — the decision-to-anything construction with
4006
+ // `risk` in the source slot. Both cross-domain, so containment is untouched.
4007
+ "risk_threatens_node",
4008
+ "risk_mitigated_by_node"
3944
4009
  ];
3945
4010
  var UPG_POLYMORPHIC_EDGE_FAMILIES = {
3946
4011
  "universal semantic verbs": ["node_informs_node", "node_constrains_node", "node_inspires_node"],
@@ -3979,7 +4044,14 @@ var UPG_POLYMORPHIC_EDGE_FAMILIES = {
3979
4044
  // `document_describes_*` set, which is not polymorphic at all: those are
3980
4045
  // enumerated edges saying a document is ABOUT a thing. Transclusion says it
3981
4046
  // RENDERS the thing, and the difference is the whole reason the edge exists.
3982
- "document transclusion": ["document_transcludes_node"]
4047
+ "document transclusion": ["document_transcludes_node"],
4048
+ // 0.35.0. Its own family and deliberately NOT merged into
4049
+ // 'decision-to-anything', which it mirrors structurally. The two say different
4050
+ // things: a decision INFLUENCES what it touches, a risk ENDANGERS what it
4051
+ // threatens, and a partition that merged them on shape alone would assert
4052
+ // those are one relation. Two members because exposure has two directions,
4053
+ // and collapsing them would lose which way an edge points.
4054
+ "risk exposure": ["risk_threatens_node", "risk_mitigated_by_node"]
3983
4055
  };
3984
4056
  var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
3985
4057
  function isPolymorphicEdge(key) {
@@ -6544,6 +6616,378 @@ function isLegacyProductStage(stage) {
6544
6616
  return typeof stage === "string" && stage.toLowerCase() in LEGACY_PRODUCT_STAGES;
6545
6617
  }
6546
6618
 
6619
+ // src/catalog/entity-descriptions.ts
6620
+ var UPG_ENTITY_DESCRIPTIONS = Object.freeze({
6621
+ // Strategy
6622
+ product: "The product being created, the root of the graph",
6623
+ outcome: "A desired business or user outcome",
6624
+ objective: "A strategic goal (OKR)",
6625
+ key_result: "A measurable result tied to an objective",
6626
+ metric: "A unified metric that measures progress, health, or behaviour across the product",
6627
+ metric_quality_assessment: "An assessment of whether a metric is well-defined, measurable, and aligned to a meaningful outcome.",
6628
+ vision: "A long-term aspirational statement of the future state",
6629
+ mission: "The purpose and reason the product exists",
6630
+ strategic_theme: "A high-level strategic focus area for a planning period. Its own time_horizon is now deprecated: promote the period to a planning_cycle and link the two with strategic_theme_scoped_to_planning_cycle, so themes share one dated, nestable interval instead of a drifting per-theme label.",
6631
+ initiative: "A large coordinated effort to achieve a strategic goal",
6632
+ capability: "An ability that enables value delivery",
6633
+ value_stream: "An end-to-end flow delivering value to the customer",
6634
+ strategic_pillar: "A foundational principle that guides decisions",
6635
+ strategic_question: "An open strategic question a plan must resolve (of coordination, ownership, sequencing, or which bet to place), held explicitly until a decision retires it; the strategy sibling of research_question and design_question",
6636
+ assumption: "A belief taken as true that underpins a strategy",
6637
+ decision: "A recorded decision with context, rationale, and consequences",
6638
+ constraint: "A limit, requirement, or ceiling the product must respect, whether a self-imposed principle or an externally imposed boundary",
6639
+ // User
6640
+ persona: "An archetype representing a user segment",
6641
+ job: "Job To Be Done: what the user is trying to accomplish",
6642
+ need: "A user need, pain, desire, or constraint",
6643
+ desired_outcome: "What the user hopes to achieve",
6644
+ job_step: "A discrete step within a Job To Be Done",
6645
+ switching_cost: "A barrier preventing users from switching alternatives",
6646
+ // Discovery
6647
+ opportunity: "A validated gap worth solving",
6648
+ solution: "A proposed approach to address an opportunity",
6649
+ feasibility_study: "An assessment of technical or business viability",
6650
+ design_sprint: "A time-boxed sprint to prototype and validate ideas",
6651
+ // Validation
6652
+ hypothesis: "A testable belief about a solution",
6653
+ experiment: "A test designed to validate a hypothesis",
6654
+ experiment_plan: "An experiment plan describing the hypothesis, setup, success criteria, and methodology before a test runs.",
6655
+ experiment_run: "An execution instance of an experiment that records actual conditions, observations, and raw results.",
6656
+ learning: "An insight gained from an experiment",
6657
+ test_plan: "A structured plan for testing a hypothesis",
6658
+ evidence: "Data supporting or refuting a hypothesis",
6659
+ research_plan: "A plan for gathering evidence to validate a draft entity",
6660
+ // Market Intelligence
6661
+ competitor: "A competing product or company",
6662
+ competitor_feature: "A feature offered by a competitor",
6663
+ market_trend: "An emerging trend in the market",
6664
+ market_segment: "A distinct group of potential customers",
6665
+ competitive_analysis: "A structured analysis of the competitive landscape",
6666
+ classification_axis: "A single dimension in a competitive classification matrix that defines one axis of comparison across alternatives.",
6667
+ classification_value: "A position or value on a classification axis, representing where one option lands on that dimension.",
6668
+ competitor_signal: "An observed competitor move or datapoint (a pricing change, launch, or hire) that feeds competitive analysis",
6669
+ // User Research
6670
+ research_study: "A planned research activity",
6671
+ insight: "A synthesised finding from research",
6672
+ participant: "A person participating in research",
6673
+ observation: "A specific behaviour or statement observed",
6674
+ quote: "A direct quote from a research participant",
6675
+ affinity_cluster: "A group of related observations",
6676
+ research_question: "A question guiding a research study",
6677
+ interview_guide: "A structured guide for conducting interviews",
6678
+ survey_response: "A response to a survey instrument",
6679
+ // Experience Design
6680
+ user_journey: "An end-to-end map of a user experience",
6681
+ journey_step: "A single step in a user journey",
6682
+ journey_phase: "A named phase in a user journey that groups a sequence of actions the user performs to reach a milestone.",
6683
+ journey_action: "A single step a user takes within a journey phase, the atomic unit of observable user behaviour.",
6684
+ user_flow: "A navigation path through the product",
6685
+ screen: "A distinct screen or view in the product",
6686
+ screen_state: "A specific state of a screen (e.g., empty, loading, error)",
6687
+ surface: "A place inside a screen, the features that occupy it, and the rule that decides who wins it",
6688
+ design_question: "An open design problem to explore",
6689
+ design_concept: "A possible design direction or approach",
6690
+ prototype: "An interactive mockup for testing",
6691
+ wireframe: "A low-fidelity structural layout",
6692
+ annotation: "A note or markup on a design artifact",
6693
+ interaction_spec: "A specification for an interaction behaviour",
6694
+ // Design System
6695
+ design_component: "A reusable UI component",
6696
+ design_token: "A design token (colour, spacing, typography)",
6697
+ design_system: "The root design system entity",
6698
+ design_pattern: "A documented design pattern",
6699
+ design_guideline: "A design usage guideline",
6700
+ // Brand Identity
6701
+ brand_identity: "The root brand identity entity",
6702
+ brand_colour: "A brand colour definition",
6703
+ brand_typography: "Brand typography specifications",
6704
+ brand_voice: "Brand voice and tone guidelines",
6705
+ brand_logo: "A brand logo or mark",
6706
+ brand_imagery: "Brand imagery and photography guidelines",
6707
+ // Product Specification
6708
+ feature: "A product capability or feature",
6709
+ feature_area: "A grouping of related features",
6710
+ epic: "A large body of work that can be broken into stories",
6711
+ user_story: `A user's goal and the value they expect, in the "As a\u2026 I want\u2026 So that\u2026" format. Now also a first-class plannable unit (priority, effort, assignee, due_date) that schedules into a planning_cycle, and it round-trips an external board's column via workflow_state.`,
6712
+ acceptance_criterion: "A condition that must be met for a story to be done",
6713
+ release: "A shipped version of the product",
6714
+ task: "A unit of work within a story or epic",
6715
+ bug: "A defect or unexpected behaviour",
6716
+ roadmap: "A strategic plan of features and milestones",
6717
+ roadmap_item: "An item on the product roadmap",
6718
+ theme: "A strategic grouping of related features",
6719
+ roadmap_theme: "A customer problem used as the organising unit of a roadmap",
6720
+ changelog: "A record of changes shipped in a release",
6721
+ planning_cycle: "The cadence axis of a plan: a dated interval a team plans in, be it a sprint, iteration, quarter, or program increment. Cycles self-nest, so a fine iteration sits inside a coarse period to form a granularity ladder.",
6722
+ configuration_axis: "A named dimension along which the product's composition differs: a feature flag, a plan tier, a permission level, a beta programme. The stored graph is the union of the family; one configuration is a projection of it.",
6723
+ // Engineering
6724
+ bounded_context: "A DDD bounded context defining a service boundary",
6725
+ service: "A deployable service or microservice",
6726
+ domain_event: "An event published when something happens in the domain",
6727
+ api_contract: "An API contract or specification",
6728
+ technical_debt_item: "A known piece of technical debt",
6729
+ feature_flag: "A feature toggle for controlled rollout",
6730
+ deployment: "A deployment event",
6731
+ aggregate: "A DDD aggregate root",
6732
+ domain_entity: "A DDD domain entity: a core object with a distinct identity and lifecycle inside a bounded context",
6733
+ value_object: "A DDD value object",
6734
+ command: "A DDD command (write operation)",
6735
+ read_model: "A DDD read model (query projection)",
6736
+ api_endpoint: "A specific API endpoint",
6737
+ database_schema: "A database schema definition",
6738
+ queue_topic: "A message queue topic",
6739
+ build_artifact: "A build output (binary, container image)",
6740
+ code_repository: "A source code repository",
6741
+ library_dependency: "A third-party library dependency",
6742
+ integration_pattern: "An integration pattern (saga, event sourcing, etc.)",
6743
+ external_api: "An external API consumed by the product",
6744
+ data_flow: "A data flow between systems",
6745
+ investigation: "An investigation into an issue or incident",
6746
+ root_cause: "An identified root cause of an issue",
6747
+ symptom: "A symptom of a problem",
6748
+ fix: "A fix applied to resolve an issue",
6749
+ // Growth
6750
+ funnel: "A conversion funnel tracking user progression",
6751
+ funnel_step: "A stage within a conversion funnel",
6752
+ acquisition_channel: "A channel through which users are acquired",
6753
+ growth_campaign: "A growth-focused campaign",
6754
+ cohort: "A group of users sharing a common characteristic",
6755
+ behavioral_segment: "A user segment based on behaviour",
6756
+ growth_loop: "A self-reinforcing growth cycle",
6757
+ variant: "A variant in an A/B test",
6758
+ attribution_model: "A model for attributing conversions to touchpoints",
6759
+ // Business Model
6760
+ business_model: "The business model canvas or definition",
6761
+ value_proposition: "A unique value offered to customers",
6762
+ revenue_stream: "A source of revenue",
6763
+ pricing_tier: "A pricing tier or plan",
6764
+ cost_structure: "A cost category or structure",
6765
+ unit_economics: "Per-unit economic metrics (CAC, LTV, etc.)",
6766
+ partnership: "A strategic partnership",
6767
+ key_resource: "A key resource required by the business",
6768
+ key_activity: "A key activity the business performs",
6769
+ target_customer_segment: "A target customer segment",
6770
+ customer_relationship: "A type of customer relationship",
6771
+ distribution_channel: "A channel for delivering value to customers",
6772
+ // Go-To-Market
6773
+ gtm_strategy: "A go-to-market strategy",
6774
+ ideal_customer_profile: "The ideal customer profile (ICP)",
6775
+ positioning: "Product positioning statement",
6776
+ messaging: "Messaging framework and key messages",
6777
+ launch: "A product launch event",
6778
+ content_strategy: "A content strategy for thought leadership",
6779
+ sales_motion: "A repeatable sales motion",
6780
+ competitive_battle_card: "A competitive battle card for sales enablement",
6781
+ demand_gen_program: "A demand generation program",
6782
+ territory: "A sales territory",
6783
+ objection: "A common sales objection",
6784
+ rebuttal: "A rebuttal to a sales objection",
6785
+ proof_point: "Evidence supporting a sales claim",
6786
+ // Team & Organisation
6787
+ team: "A cross-functional team",
6788
+ role: "A role within a team",
6789
+ stakeholder: "A person with influence over the product",
6790
+ team_okr: "A team-level OKR",
6791
+ retrospective: "A team retrospective",
6792
+ dependency: "A cross-team or system dependency",
6793
+ department: "An organisational department",
6794
+ skill: "A skill or competency within a team",
6795
+ ceremony: "A recurring team ritual (standup, planning)",
6796
+ capacity_plan: "A plan for allocating team capacity",
6797
+ person: "A named individual (owner, stakeholder, or participant) referenced across the graph",
6798
+ // Data & Analytics
6799
+ data_source: "A data source or integration",
6800
+ event_schema: "An event schema for tracking",
6801
+ dashboard: "An analytics dashboard",
6802
+ data_model: "A data model or schema",
6803
+ data_quality_rule: "A data quality validation rule",
6804
+ data_product: "A curated, reusable data asset",
6805
+ data_pipeline: "An automated pipeline for data transformation",
6806
+ data_lineage: "A record of data origin and transformations",
6807
+ glossary_term: "A defined term for shared understanding",
6808
+ data_domain: "A logical grouping of related data assets",
6809
+ report: "A structured analytical report",
6810
+ // Customer Success
6811
+ support_ticket: "Customer support request or issue",
6812
+ customer_feedback: "Voice-of-customer input",
6813
+ churn_reason: "Why a customer left",
6814
+ customer_health_score: "A composite health score for a customer",
6815
+ playbook: "A standard operating procedure for CS",
6816
+ service_level_agreement: "A service level agreement with customers",
6817
+ customer_journey_stage: "A stage in the post-sale customer journey",
6818
+ touchpoint: "A customer interaction touchpoint",
6819
+ success_milestone: "A customer success milestone",
6820
+ service_blueprint: "A blueprint of the full service delivery",
6821
+ // Content & Knowledge
6822
+ content_piece: "A piece of content (article, video, etc.)",
6823
+ knowledge_base_article: "A knowledge base article",
6824
+ brand_asset: "A brand asset (logo, image, etc.)",
6825
+ internal_doc: "An internal documentation page",
6826
+ prompt_template: "A reusable prompt template",
6827
+ content_calendar: "A content publication calendar",
6828
+ content_theme: "A thematic grouping for content",
6829
+ documentation_template: "A template for documentation",
6830
+ document: "A general-purpose document",
6831
+ // Legal
6832
+ legal_entity: "A legal entity (company, subsidiary)",
6833
+ ip_asset: "An intellectual property asset",
6834
+ contract: "A legal contract",
6835
+ contract_clause: "A clause within a contract",
6836
+ privacy_policy: "A privacy policy",
6837
+ // Compliance
6838
+ compliance_requirement: "A compliance requirement",
6839
+ risk: "A risk to the product or business",
6840
+ data_contract: "A data-sharing contract",
6841
+ audit_log_policy: "An audit logging policy",
6842
+ compliance_framework: "A compliance framework (SOC 2, GDPR, etc.)",
6843
+ security_audit: "A security audit",
6844
+ // DevOps & Platform
6845
+ service_level_indicator: "A service level indicator (SLI)",
6846
+ service_level_objective: "A service level objective (SLO)",
6847
+ error_budget: "An error budget for a service",
6848
+ incident: "A production incident",
6849
+ postmortem: "A post-incident review",
6850
+ runbook: "A runbook for incident response",
6851
+ monitor: "A monitoring check",
6852
+ alert_rule: "An alerting rule",
6853
+ ci_pipeline: "A CI/CD pipeline",
6854
+ release_strategy: "A release strategy (canary, blue-green, etc.)",
6855
+ on_call_rotation: "An on-call rotation schedule",
6856
+ infrastructure_component: "An infrastructure component (server, CDN, etc.)",
6857
+ // Security
6858
+ threat_model: "A threat model for the system",
6859
+ threat: "A specific security threat",
6860
+ vulnerability: "A known vulnerability",
6861
+ security_control: "A security control or mitigation",
6862
+ security_policy: "A security policy",
6863
+ penetration_test: "A penetration test",
6864
+ security_review: "A security review",
6865
+ data_classification: "A data classification level",
6866
+ access_policy: "An access control policy",
6867
+ // Accessibility
6868
+ a11y_standard: "An accessibility standard (WCAG, Section 508)",
6869
+ a11y_guideline: "An accessibility guideline",
6870
+ a11y_audit: "An accessibility audit",
6871
+ a11y_issue: "An accessibility issue",
6872
+ a11y_annotation: "An accessibility annotation on a design",
6873
+ // Quality Assurance
6874
+ test_suite: "A suite of related tests",
6875
+ test_case: "An individual test case",
6876
+ qa_session: "An exploratory QA session",
6877
+ regression_test: "A regression test",
6878
+ test_coverage_report: "A test coverage report",
6879
+ test_environment: "A testing environment",
6880
+ test_result: "A test execution result",
6881
+ // Customer Feedback
6882
+ feedback_program: "A structured feedback collection program",
6883
+ feature_request: "A user-submitted feature request",
6884
+ feedback_vote: "A vote on a feature request or feedback item",
6885
+ nps_campaign: "A Net Promoter Score survey campaign",
6886
+ user_advisory_board: "A panel of users providing ongoing feedback",
6887
+ beta_program: "A beta testing program",
6888
+ feedback_theme: "A recurring theme across feedback items",
6889
+ // Pricing & Packaging
6890
+ pricing_strategy: "An overarching pricing strategy",
6891
+ package: "A bundled package of features at a price point",
6892
+ discount_strategy: "A strategy for offering discounts",
6893
+ trial_config: "Configuration for a free trial",
6894
+ paywall: "A paywall gating premium features",
6895
+ // AI & Machine Learning
6896
+ ai_model: "An AI or ML model used within the product",
6897
+ prompt_version: "A version of a prompt template",
6898
+ eval_benchmark: "A benchmark for evaluating AI quality",
6899
+ eval_run: "An evaluation run against a benchmark",
6900
+ ai_cost_tracker: "A tracker for AI inference costs",
6901
+ hallucination_report: "A report of an AI hallucination",
6902
+ ai_guardrail: "A guardrail for AI safety",
6903
+ model_comparison: "A comparison between AI models",
6904
+ ai_experiment: "An AI-focused experiment",
6905
+ ai_dataset: "A dataset for AI training or evaluation",
6906
+ ai_trace: "An AI inference trace",
6907
+ // Workflows & Agents
6908
+ workflow_template: "A reusable workflow template",
6909
+ workflow_run: "An execution of a workflow",
6910
+ agent_definition: "An autonomous agent definition",
6911
+ agent_session: "An agent working session",
6912
+ review_gate: "A human review checkpoint in a workflow",
6913
+ approval_record: "A record of an approval decision",
6914
+ agent_skill: "A capability of an agent",
6915
+ agent_hook: "A trigger connecting an agent to an event",
6916
+ workflow_artifact: "An output artifact from a workflow run",
6917
+ agent_task: "A discrete unit of agent work",
6918
+ // Portfolio
6919
+ organization: "The top-level organisational entity",
6920
+ portfolio: "A grouping of products by strategic axis",
6921
+ product_area: "A grouping of products by organisational axis",
6922
+ // Sales & Revenue
6923
+ account: "A customer account",
6924
+ contact: "A person within an account",
6925
+ lead: "An inbound lead",
6926
+ deal: "An active sales opportunity",
6927
+ pipeline_sales: "A sales pipeline",
6928
+ pipeline_stage: "A stage in the sales pipeline",
6929
+ quote_document: "A formal quote document",
6930
+ subscription: "A recurring subscription",
6931
+ invoice: "An invoice for billing",
6932
+ forecast: "A revenue forecast",
6933
+ // Program Management
6934
+ program: "A program coordinating multiple projects",
6935
+ project: "A project within a program",
6936
+ milestone: "A key date or achievement",
6937
+ risk_register: "A register of project risks",
6938
+ change_request: "A request to change project scope",
6939
+ deliverable: "A deliverable from a project",
6940
+ resource_allocation: "An allocation of team capacity",
6941
+ status_report: "A project status report",
6942
+ // Marketing
6943
+ marketing_strategy: "A marketing strategy",
6944
+ marketing_channel: "A marketing channel",
6945
+ marketing_campaign_plan: "A marketing campaign plan",
6946
+ email_sequence: "An email nurture sequence",
6947
+ social_post: "A social media post",
6948
+ seo_keyword: "An SEO keyword target",
6949
+ ad_creative: "An ad creative",
6950
+ press_release: "A press release",
6951
+ event: "A marketing event",
6952
+ community_initiative: "A community engagement initiative",
6953
+ // Localisation
6954
+ locale: "A supported locale (language + region)",
6955
+ translation_key: "A translatable string key",
6956
+ translation_bundle: "A bundle of translation keys",
6957
+ locale_config: "Per-locale configuration settings",
6958
+ cultural_adaptation: "A region-specific cultural adaptation",
6959
+ regional_pricing: "Location-based pricing",
6960
+ // Customer Education
6961
+ education_program: "A customer education program",
6962
+ tutorial: "A step-by-step tutorial",
6963
+ walkthrough: "A guided product walkthrough",
6964
+ webinar: "A live or recorded webinar",
6965
+ certification: "A certification program",
6966
+ help_video: "A help video",
6967
+ learning_path: "A structured learning path",
6968
+ // Partners & Ecosystem
6969
+ partner_program: "A partner program",
6970
+ partner_tier: "A partner tier level",
6971
+ api_ecosystem: "An API ecosystem",
6972
+ marketplace_listing: "A marketplace listing",
6973
+ developer_portal: "A developer portal",
6974
+ integration_partner: "An integration partner",
6975
+ partner_revenue_share: "A partner revenue sharing arrangement",
6976
+ // Foundations
6977
+ operating_lifecycle: "A canonical, ordered (often cyclic) operating process a product's journey phases map onto",
6978
+ operating_stage: "One ordered stage of an operating_lifecycle",
6979
+ specification: "A canonical open standard or specification that primitives and products conform to",
6980
+ primitive: "A canonical reusable building block defined by a specification",
6981
+ // Workspace
6982
+ workspace: "A spatial thinking space for arranging entities",
6983
+ framework_exercise: "A recorded run of a product framework (RICE, OST, and the like) over specific entities, holding its inputs and outputs",
6984
+ composition: "A published arrangement of graph entities that has an identity of its own: a gallery, a board, a deck. Membership can be derived by running its query, position is authored by a person, and both are stored because both are real.",
6985
+ capture: "A dated, hashed rendition of something already in the graph: a screenshot of a surface, a PDF of a report, an export of a canvas. The subject stays a graph node; the capture records what it looked like at one moment, and the content hash says whether it still does."
6986
+ });
6987
+ function getEntityDescription(type) {
6988
+ return UPG_ENTITY_DESCRIPTIONS[type];
6989
+ }
6990
+
6547
6991
  // src/shapes/base-node.ts
6548
6992
  var BASE_NODE_FIELD_PRESENCE = {
6549
6993
  id: true,
@@ -6970,6 +7414,11 @@ var UPG_VALID_CHILDREN = {
6970
7414
  competitor: ["competitor_feature", "competitor_signal"],
6971
7415
  classification_axis: ["classification_value"],
6972
7416
  // ── UX Research hierarchy ───────────────────────────────────────────────────
7417
+ // (0.35.0) 'quote' added: a study is the provenance parent of the verbatim
7418
+ // quotes it captured, pairing with `research_study_captures_quote`. A quote
7419
+ // now has three declared parents — observation (raw capture), insight
7420
+ // (synthesis) and research_study (provenance) — which is multi-parent
7421
+ // GRAMMAR; per-instance parentage stays single via `parent_id`.
6973
7422
  research_study: [
6974
7423
  "participant",
6975
7424
  "observation",
@@ -6977,7 +7426,8 @@ var UPG_VALID_CHILDREN = {
6977
7426
  "research_question",
6978
7427
  "interview_guide",
6979
7428
  "insight",
6980
- "survey_response"
7429
+ "survey_response",
7430
+ "quote"
6981
7431
  ],
6982
7432
  observation: ["quote"],
6983
7433
  // F6 (UPG-672): affinity_cluster owns the observations it groups, in addition
@@ -7034,7 +7484,12 @@ var UPG_VALID_CHILDREN = {
7034
7484
  // story_task collapsed into canonical task. feature now owns
7035
7485
  // task directly; story-derived tasks implement user_story via
7036
7486
  // task_implements_user_story edge.
7037
- feature: ["epic", "bug", "task"],
7487
+ // (0.35.0) 'user_story' added, pairing with `feature_specified_by_user_story`.
7488
+ // The epic rung is optional in UPG, so a feature that skips it could not
7489
+ // reach the stories that specify it and `user_story` had a single declared
7490
+ // parent. Same widening shape as the 0.23.0 epic twins: an existing leaf type
7491
+ // gains a second, SHALLOWER parent, verbs inherited from the deeper edge.
7492
+ feature: ["epic", "bug", "task", "user_story"],
7038
7493
  feature_area: ["feature", "feature_area"],
7039
7494
  // epic owns user_story (the templated promise) and, mirroring feature, may
7040
7495
  // also directly contain bug/task — heterogeneous imported tickets that belong
@@ -7820,6 +8275,37 @@ var UPG_SCALES = {
7820
8275
  // the existing classify-edge population, so nothing needs re-stamping.
7821
8276
  friendly_aliases: { low: 2, medium: 3, high: 4 }
7822
8277
  },
8278
+ // ── Likelihood ─────────────────────────────────────────────────────────────
8279
+ /**
8280
+ * (0.35.0) How likely a future event is to occur.
8281
+ *
8282
+ * Added because the nine pre-existing ladders had no probability ladder, and
8283
+ * `likelihood` was resolving to `confidence_5` — which is EPISTEMIC. Confidence
8284
+ * says how sure you are of a judgment ("Guessing → Data-backed"); likelihood
8285
+ * says how probable the event is. A risk you are certain about and a risk that
8286
+ * is certain to happen are different facts, and one ladder cannot hold both.
8287
+ *
8288
+ * Vocabulary is ISO 31000's, which is also what `risk.likelihood`,
8289
+ * `threat.likelihood` and the Notion fixtures already say in prose.
8290
+ *
8291
+ * Polarity is `low-is-good` (rendered by the design system's direction map,
8292
+ * which is where numeric-scale polarity lives): 5 on this ladder is bad news.
8293
+ */
8294
+ likelihood_5: {
8295
+ id: "likelihood_5",
8296
+ label: "Likelihood (5-point)",
8297
+ description: "How likely this event is to occur",
8298
+ min: 1,
8299
+ max: 5,
8300
+ steps: 5,
8301
+ points: [
8302
+ { value: 1, label: "Rare", description: "Would be surprising; no known precedent" },
8303
+ { value: 2, label: "Unlikely", description: "Could happen, but not expected" },
8304
+ { value: 3, label: "Possible", description: "Might happen; roughly even odds" },
8305
+ { value: 4, label: "Likely", description: "Expected to happen absent intervention" },
8306
+ { value: 5, label: "Almost certain", description: "Expected to happen; plan for it" }
8307
+ ]
8308
+ },
7823
8309
  // ── Effort ─────────────────────────────────────────────────────────────────
7824
8310
  effort_5: {
7825
8311
  id: "effort_5",
@@ -7892,12 +8378,17 @@ var PROPERTY_SCALE_MAP = {
7892
8378
  // discovery, validation, sales, product-spec
7893
8379
  current_confidence: "confidence_5",
7894
8380
  // validation/hypothesis (UPG-579 Option B)
7895
- likelihood: "confidence_5",
7896
- // market/risk (UPG-579 Option B)
7897
8381
  probability: "confidence_5",
7898
8382
  // sales/forecast (UPG-579 Option B)
7899
8383
  qualification_score: "confidence_5",
7900
8384
  // sales/deal — how confident the qualification is (0.24.0)
8385
+ // ── Likelihood ───────────────────────────────────────────────────────────
8386
+ // (0.35.0) Moved off confidence_5. `likelihood` is how probable the event is,
8387
+ // not how well-evidenced the judgment is; the two were sharing one ladder
8388
+ // because no probability ladder existed. Carries market/risk (`risk`,
8389
+ // `market_trend`) and `security.threat.likelihood` in one line.
8390
+ likelihood: "likelihood_5",
8391
+ // compliance/risk, security/threat, market
7901
8392
  // ── Effort ───────────────────────────────────────────────────────────────
7902
8393
  effort: "effort_5",
7903
8394
  // discovery
@@ -7927,9 +8418,18 @@ var PROPERTY_SCALE_MAP = {
7927
8418
  // rarity : VRIO distinctiveness; no canonical named scale fits.
7928
8419
  // strength : deprecated (validation/evidence; superseded by weight).
7929
8420
  };
8421
+ var PROPERTY_SCALE_MAP_BY_ENTITY = {
8422
+ risk: {
8423
+ // Severity of consequences, not magnitude of benefit. See above.
8424
+ impact: "severity_5",
8425
+ // The DEPRECATED spelling of `likelihood`, held on the same ladder as its
8426
+ // replacement for the length of the deprecation window. See above.
8427
+ probability: "likelihood_5"
8428
+ }
8429
+ };
7930
8430
  var DEFAULT_SCALE_ID = "scale_5";
7931
- function getPropertyDefaultScale(_entityType, propertyName) {
7932
- return PROPERTY_SCALE_MAP[propertyName] ?? DEFAULT_SCALE_ID;
8431
+ function getPropertyDefaultScale(entityType, propertyName) {
8432
+ return PROPERTY_SCALE_MAP_BY_ENTITY[entityType]?.[propertyName] ?? PROPERTY_SCALE_MAP[propertyName] ?? DEFAULT_SCALE_ID;
7933
8433
  }
7934
8434
  function getPropertiesForScale(scaleId) {
7935
8435
  return Object.entries(PROPERTY_SCALE_MAP).filter(([, id]) => id === scaleId).map(([property]) => property);
@@ -8020,6 +8520,29 @@ var UPG_ENUM_SCALES = {
8020
8520
  { value: "guideline", label: "Guideline", description: "Recommendation: encouraged and expected in most cases, but not enforced." }
8021
8521
  ]
8022
8522
  },
8523
+ // ── EngagementPosture ────────────────────────────────────────────────
8524
+ /**
8525
+ * (0.35.0) The third axis of stakeholder canon, beside the power/interest
8526
+ * grid `influence` / `interest` carry. Those two say how much weight a
8527
+ * stakeholder has and how much they care; neither says which WAY they lean,
8528
+ * and "who blocks this?" is the question a stakeholder map is drawn to answer.
8529
+ *
8530
+ * `tone_direction: 'high-is-good'` reading champion → blocker: the first
8531
+ * value is the desirable end, matching HealthStatus's convention.
8532
+ */
8533
+ EngagementPosture: {
8534
+ id: "EngagementPosture",
8535
+ label: "Engagement posture",
8536
+ description: "Which way a stakeholder leans on the thing at hand: actively for it, against it, or neither.",
8537
+ tone_direction: "high-is-good",
8538
+ values: [
8539
+ { value: "champion", label: "Champion", description: "Actively advocates for it and spends their own capital doing so." },
8540
+ { value: "supporter", label: "Supporter", description: "In favour and will say so when asked, but does not drive it." },
8541
+ { value: "neutral", label: "Neutral", description: "No position taken; neither helps nor hinders." },
8542
+ { value: "skeptic", label: "Skeptic", description: "Unconvinced and voices objections, but is not blocking." },
8543
+ { value: "blocker", label: "Blocker", description: "Actively opposes, and holds enough leverage to stop it." }
8544
+ ]
8545
+ },
8023
8546
  // ── ProxyConfidence ─────────────────────────────────────────────────
8024
8547
  ProxyConfidence: {
8025
8548
  id: "ProxyConfidence",
@@ -8532,6 +9055,62 @@ function getDeprecatedTypes() {
8532
9055
  return deprecated;
8533
9056
  }
8534
9057
  var UPG_PROPERTY_MIGRATIONS = {
9058
+ // ── v0.35.0: risk.probability → risk.likelihood, STAGED ─────────────────────
9059
+ //
9060
+ // Deliberately EMPTY, and the emptiness is the design rather than an omission.
9061
+ // The rename is staged: `RiskProperties.probability` is kept, marked
9062
+ // `@deprecated since="0.35.0" removeIn="1.0.0"`, and still read. 0.35.0 changes
9063
+ // what writers emit (`likelihood`) and what readers prefer; it changes no
9064
+ // stored bytes, so there is nothing for a load-time rule to do. A graph
9065
+ // written before 0.35.0 carries `probability` and no `likelihood` and reads
9066
+ // correctly — which is what staging BUYS, and a rule here would spend it.
9067
+ //
9068
+ // Three reasons there is no executable rule, in order of weight:
9069
+ //
9070
+ // 1. A staged deprecation that rewrites data is not staged. The point of the
9071
+ // `epic.estimate` → `effort` pattern this follows is that both names live
9072
+ // through the window and the consumer chooses; migrating the key on load
9073
+ // would end the window on the first read and break any reader still
9074
+ // asking for the old name.
9075
+ // 2. The executable step belongs at the REMOVAL version, not here. That is
9076
+ // the two-step the `removeIn="0.5.0"` properties followed: declared
9077
+ // deprecated with a deadline at 0.4.0, dropped by `drop_props` rules in
9078
+ // the '0.5.0' block below. `probability` is scheduled for the same
9079
+ // treatment at 1.0.0.
9080
+ // 3. No kind fits, and inventing one would be the larger change. Checked
9081
+ // against the APPLIER, not just the type union:
9082
+ // - `drop_props` deletes, which is the 1.0.0 step, not this one.
9083
+ // - `lift_property_to_top_level` targets `UPGBaseNode`, and `likelihood`
9084
+ // is a property, not a top-level field.
9085
+ // - `rename_top_level` renames a top-level field, same mismatch.
9086
+ // - `remap_property_value` is the near miss and still misses twice: it
9087
+ // `break`s unless `typeof oldValue === 'string'` (these values are
9088
+ // `UPGAssessment` objects, or bare numbers in older graphs), and its
9089
+ // `to_property` split OVERWRITES the source with `reset_value`, which
9090
+ // is precisely the staging this release is buying.
9091
+ // - `reshape_value_to_assessment` wraps a bare number on a named scale;
9092
+ // it does not move a key.
9093
+ // The edge-side `kind: 'rename'` (UPG_EDGE_MIGRATIONS, e.g. 0.8.0's
9094
+ // `theme_groups_feature` -> `roadmap_theme_groups_feature`) renames an
9095
+ // EDGE TYPE across instances and has no property-key analogue. Adding one
9096
+ // is new spec surface: a ratified decision, not a side effect of one
9097
+ // property rename.
9098
+ //
9099
+ // The scale moves ride along and need no rule either. `PROPERTY_SCALE_MAP`
9100
+ // resolves `likelihood` to `likelihood_5` (which carries `threat.likelihood`
9101
+ // off `confidence_5` in the same line, since the map is keyed by name) and
9102
+ // `PROPERTY_SCALE_MAP_BY_ENTITY` resolves `risk.impact` to `severity_5`. Both
9103
+ // resolve at READ time, and an assessment only overrides them by PINNING a
9104
+ // `scale_id` on the stored value. Measured across all 36 `.upg` files in the
9105
+ // repo: zero assessments pin a `scale_id` on `likelihood`, `probability` or
9106
+ // `impact`, so there is nothing stamped to re-stamp. One that does pin a
9107
+ // `scale_id` keeps it, by the existing contract.
9108
+ //
9109
+ // What lands at 1.0.0, written down now so it is not rediscovered:
9110
+ // { kind: 'drop_props', type: 'risk', drop_props: ['probability'],
9111
+ // reason: '1.0.0: risk.probability was @deprecated since 0.35.0 with
9112
+ // removeIn="1.0.0"; superseded by `likelihood` on likelihood_5.' }
9113
+ "0.35.0": [],
8535
9114
  "0.32.0": [
8536
9115
  // C3 — archive becomes a base-node axis. WorkspaceProperties.archived /
8537
9116
  // archived_at shipped the orthogonal-archive idea for exactly one type;
@@ -13936,10 +14515,23 @@ var UPG_PROPERTY_SCHEMA = {
13936
14515
  // RiskProperties: Risk.
13937
14516
  risk: {
13938
14517
  risk_type: { type: "string", enum: ["technical", "business", "legal", "security", "operational", "program"], description: "Domain the risk belongs to. The single kind axis for `risk`: there is no second classification vocabulary. `program` added in v0.26.0 so the retired `risk_item` (Program Management) type has a home on the canonical `risk` after consolidation, rather than a parallel `risk_domain` field free to drift from this one." },
14518
+ likelihood: {
14519
+ type: "assessment",
14520
+ scale_id: "likelihood_5",
14521
+ description: "How likely this risk is to materialise. Rated on `likelihood_5` (Rare \u2192 Almost certain).",
14522
+ notes: 'Canonical name since 0.35.0, superseding `probability` below. Three reasons, in order of weight. (1) `probability` was one name for two incompatible types (`UPGAssessment` here, a bare `number` on `forecast.probability`), and `PROPERTY_SCALE_MAP` is keyed by name alone, so a sales percentage and a risk judgment resolved to the same ladder. (2) `likelihood` is already the spec\'s own word: the RISK_ITEM lifecycle prose says "Likelihood and impact have been evaluated", and `threat.likelihood` has been a `UPGAssessment` all along: one name, one type, one ladder. (3) ISO 31000 says likelihood. The ladder moved with the name: `likelihood_5` is new in 0.35.0 because no probability ladder existed and `confidence_5` is epistemic. It says how sure the assessor is, not how likely the event is.',
14523
+ properties: {
14524
+ value: { type: "number", description: "The numeric value, used for computation." },
14525
+ label: { type: "string", description: "The qualitative label (what the assessor meant)." },
14526
+ scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
14527
+ normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
14528
+ },
14529
+ required: ["value", "label"]
14530
+ },
13939
14531
  probability: {
13940
14532
  type: "assessment",
13941
- scale_id: "confidence_5",
13942
- description: "How likely this risk is to materialise (1 = unlikely, 5 = near certain)",
14533
+ scale_id: "likelihood_5",
14534
+ description: 'How likely this risk is to materialise (1 = unlikely, 5 = near certain). @deprecated since="0.35.0" removeIn="1.0.0". Use `likelihood`, which is the spec\'s own word for this in the risk lifecycle and on `threat`. Both names resolve to the same `likelihood_5` ladder for the length of the deprecation window (`PROPERTY_SCALE_MAP_BY_ENTITY.risk.probability`, Captain-ratified 2026-08-22): a deprecated field that renders on a DIFFERENT ladder from its replacement would make one stored 4 read "Confident" under the old name and "Likely" under the new one, which is the data changing meaning at the rename, which is the exact thing staging exists to prevent. `forecast.probability` is untouched and stays on `confidence_5`. STAGED, not renamed. The field is KEPT and still read: 0.35.0 changes what writers emit and what readers prefer, and changes NO stored bytes. A graph written before 0.35.0 carries `probability` and no `likelihood`, and reads correctly, which is the whole point of staging it. Writers: emit `likelihood`. Readers: prefer `likelihood`, fall back to `probability`. The fallback is a CONTRACT on consumers, not executable spec machinery, exactly as it is for `epic.estimate` \u2192 `effort`. `removeIn="1.0.0"` is a deadline, not a wish: at 1.0.0 this field is dropped by a `drop_props` rule in `UPG_PROPERTY_MIGRATIONS`, the same two-step the `removeIn="0.5.0"` properties followed (declared at 0.4.0, dropped by the 0.5.0 rules). Until then there is deliberately no executable rule: see the `\'0.35.0\'` block in `grammar/migrations.ts`.',
13943
14535
  properties: {
13944
14536
  value: { type: "number", description: "The numeric value, used for computation." },
13945
14537
  label: { type: "string", description: "The qualitative label (what the assessor meant)." },
@@ -13950,8 +14542,9 @@ var UPG_PROPERTY_SCHEMA = {
13950
14542
  },
13951
14543
  impact: {
13952
14544
  type: "assessment",
13953
- scale_id: "impact_5",
13954
- description: "Severity of consequences if the risk materialises (1 = negligible, 5 = catastrophic)",
14545
+ scale_id: "severity_5",
14546
+ description: "Severity of consequences if the risk materialises. Rated on `severity_5` (Mild inconvenience \u2192 Blocker), NOT the benefit-framed `impact_5`.",
14547
+ notes: "The ladder is set by `PROPERTY_SCALE_MAP_BY_ENTITY.risk.impact` (0.35.0), the per-entity override layer. `impact` legitimately means magnitude of BENEFIT on discovery and market entities, where high is good; on a risk it means severity of harm, where high is bad. Sharing `impact_5` rendered a catastrophic risk green.",
13955
14548
  properties: {
13956
14549
  value: { type: "number", description: "The numeric value, used for computation." },
13957
14550
  label: { type: "string", description: "The qualitative label (what the assessor meant)." },
@@ -13960,7 +14553,7 @@ var UPG_PROPERTY_SCHEMA = {
13960
14553
  },
13961
14554
  required: ["value", "label"]
13962
14555
  },
13963
- mitigation: { type: "string", description: "Planned or implemented mitigation strategy" }
14556
+ mitigation: { type: "string", description: "Planned or implemented mitigation strategy, as prose.", notes: "Prose only. A structured list of mitigating ACTIONS is a set of edges: `risk_mitigated_by_node` (0.35.0), pointing at the decisions, features and experiments that actually do the mitigating; a string array of them is unqueryable by construction. Likewise, what the risk puts at stake is `risk_threatens_node`, not a scope-list property." }
13964
14557
  },
13965
14558
  // RiskRegisterProperties: Risk register.
13966
14559
  risk_register: {
@@ -14214,7 +14807,9 @@ var UPG_PROPERTY_SCHEMA = {
14214
14807
  normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
14215
14808
  },
14216
14809
  required: ["value", "label"]
14217
- }
14810
+ },
14811
+ engagement_posture: { type: "string", enum: ["champion", "supporter", "neutral", "skeptic", "blocker"], description: "Which way this stakeholder leans: actively for, actively against, or neither. The third axis of the stakeholder model (0.35.0), beside the power/interest grid `influence` and `interest` describe.", notes: "`influence` and `interest` are magnitudes and carry no direction: a high-influence, high-interest stakeholder can be the strongest champion or the one who kills it, and the grid renders them identically. This closed enum is what makes \"who blocks this?\" a query rather than a reading exercise. Pairs with `UPG_ENUM_SCALES.EngagementPosture`. Posture is about the person's stance; it is NOT delivery health, which belongs to work items, and NOT the relationship's operational state." },
14812
+ engagement_cadence: { type: "string", enum: ["continuous", "hourly", "daily", "weekly", "monthly", "quarterly", "yearly", "on_demand", "other"], description: "How often this stakeholder is engaged. Uses the shared `Cadence` scale.", notes: 'Typed as `Cadence` rather than free text on purpose: v0.4.0 introduced that enum precisely to retire strings like `"2x/week"`, and a cadence that cannot be compared across stakeholders cannot answer "who have we not spoken to this quarter?". WHERE you meet them (the channel) is an app-level concern and is deliberately not modelled here.' }
14218
14813
  },
14219
14814
  // StatusReportProperties: Status report.
14220
14815
  status_report: {
@@ -14472,7 +15067,7 @@ var UPG_PROPERTY_SCHEMA = {
14472
15067
  category: { type: "string", description: 'Attack or threat scenario. @example "injection", "misconfiguration", "social engineering", "supply chain"' },
14473
15068
  likelihood: {
14474
15069
  type: "assessment",
14475
- scale_id: "confidence_5",
15070
+ scale_id: "likelihood_5",
14476
15071
  description: "Likelihood (1 = theoretical, 5 = actively exploited). @example value 5 for a known, exploitable, commonly targeted pattern",
14477
15072
  properties: {
14478
15073
  value: { type: "number", description: "The numeric value, used for computation." },
@@ -21457,10 +22052,16 @@ function validateUPGDocument(doc) {
21457
22052
  }
21458
22053
  const d = doc;
21459
22054
  if (!d.upg_version || typeof d.upg_version !== "string") {
21460
- errors.push({ path: "$.upg_version", message: "upg_version is required and must be a string" });
22055
+ errors.push({
22056
+ path: "$upg.spec_version",
22057
+ message: "spec_version is required and must be a string"
22058
+ });
21461
22059
  }
21462
22060
  if (!d.exported_at || typeof d.exported_at !== "string") {
21463
- errors.push({ path: "$.exported_at", message: "exported_at is required and must be an ISO 8601 string" });
22061
+ errors.push({
22062
+ path: "$upg.provenance.exported_at",
22063
+ message: "provenance.exported_at is required and must be an ISO 8601 string"
22064
+ });
21464
22065
  }
21465
22066
  if (!d.source || typeof d.source !== "object") {
21466
22067
  errors.push({ path: "$.source", message: "source is required and must be an object" });
@@ -33228,7 +33829,7 @@ function checkHeaderSealText(text) {
33228
33829
  }
33229
33830
 
33230
33831
  // src/index.ts
33231
- var UPG_VERSION = "0.34.0";
33832
+ var UPG_VERSION = "0.35.0";
33232
33833
  var UPG_FORMAT_VERSION = "0.4.0";
33233
33834
  var MARKDOWN_FORMAT_VERSION = "0.1";
33234
33835
  var UPG_TYPES = getTypes();
@@ -33330,6 +33931,7 @@ export {
33330
33931
  PROPERTY_MODIFIERS,
33331
33932
  PROPERTY_MODIFIER_SEMANTICS,
33332
33933
  PROPERTY_SCALE_MAP,
33934
+ PROPERTY_SCALE_MAP_BY_ENTITY,
33333
33935
  QUALIFIABLE_EDGE_TYPES,
33334
33936
  RECORD_ENTITY_TYPES,
33335
33937
  REFLECT_MODES,
@@ -33366,6 +33968,7 @@ export {
33366
33968
  UPG_EDGE_PAIR_MAP,
33367
33969
  UPG_EDGE_TYPES,
33368
33970
  UPG_ENTITY_COUNT,
33971
+ UPG_ENTITY_DESCRIPTIONS,
33369
33972
  UPG_ENTITY_FILTER_SPECS,
33370
33973
  UPG_ENTITY_META,
33371
33974
  UPG_ENTITY_META_BY_ID,
@@ -33467,6 +34070,7 @@ export {
33467
34070
  getDomainIdForType,
33468
34071
  getDomainsInRing,
33469
34072
  getEdgePropertySchema,
34073
+ getEntityDescription,
33470
34074
  getEntityModifierSummary,
33471
34075
  getEnumScale,
33472
34076
  getEnumValueMeta,