@unified-product-graph/core 0.33.1 → 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/CHANGELOG.md +104 -0
- package/README.md +1 -1
- package/dist/index.cjs +2622 -1807
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +821 -65
- package/dist/index.d.ts +821 -65
- package/dist/index.js +2337 -1529
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -40,10 +40,12 @@ __export(index_exports, {
|
|
|
40
40
|
CONFIGURATION_VARIANCE_EDGE_PROPERTY_SCHEMA: () => CONFIGURATION_VARIANCE_EDGE_PROPERTY_SCHEMA,
|
|
41
41
|
CONTENT_DEPTH_WARNING_RULES: () => CONTENT_DEPTH_WARNING_RULES,
|
|
42
42
|
COVERAGE_ANTI_PATTERNS: () => COVERAGE_ANTI_PATTERNS,
|
|
43
|
+
CROSS_EDGE_KEY_ORDER: () => CROSS_EDGE_KEY_ORDER,
|
|
43
44
|
DEFAULT_ENTITY_EMOJI: () => DEFAULT_ENTITY_EMOJI,
|
|
44
45
|
DEFERRED_PROPOSED_BY_CONTRACT: () => DEFERRED_PROPOSED_BY_CONTRACT,
|
|
45
46
|
DEFER_EDGE_PROPERTY_SCHEMA: () => DEFER_EDGE_PROPERTY_SCHEMA,
|
|
46
47
|
DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK: () => DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
|
|
48
|
+
EDGE_KEY_ORDER: () => EDGE_KEY_ORDER,
|
|
47
49
|
ENGINEERING_PLATFORM_PLAYBOOK: () => ENGINEERING_PLATFORM_PLAYBOOK,
|
|
48
50
|
ENTITY_EMOJI: () => ENTITY_EMOJI,
|
|
49
51
|
EXPERIENCE_DESIGN_BRAND_PLAYBOOK: () => EXPERIENCE_DESIGN_BRAND_PLAYBOOK,
|
|
@@ -58,6 +60,7 @@ __export(index_exports, {
|
|
|
58
60
|
PROPERTY_MODIFIERS: () => PROPERTY_MODIFIERS,
|
|
59
61
|
PROPERTY_MODIFIER_SEMANTICS: () => PROPERTY_MODIFIER_SEMANTICS,
|
|
60
62
|
PROPERTY_SCALE_MAP: () => PROPERTY_SCALE_MAP,
|
|
63
|
+
PROPERTY_SCALE_MAP_BY_ENTITY: () => PROPERTY_SCALE_MAP_BY_ENTITY,
|
|
61
64
|
QUALIFIABLE_EDGE_TYPES: () => QUALIFIABLE_EDGE_TYPES,
|
|
62
65
|
RECORD_ENTITY_TYPES: () => RECORD_ENTITY_TYPES,
|
|
63
66
|
REFLECT_MODES: () => REFLECT_MODES,
|
|
@@ -94,6 +97,7 @@ __export(index_exports, {
|
|
|
94
97
|
UPG_EDGE_PAIR_MAP: () => UPG_EDGE_PAIR_MAP,
|
|
95
98
|
UPG_EDGE_TYPES: () => UPG_EDGE_TYPES,
|
|
96
99
|
UPG_ENTITY_COUNT: () => UPG_ENTITY_COUNT,
|
|
100
|
+
UPG_ENTITY_DESCRIPTIONS: () => UPG_ENTITY_DESCRIPTIONS,
|
|
97
101
|
UPG_ENTITY_FILTER_SPECS: () => UPG_ENTITY_FILTER_SPECS,
|
|
98
102
|
UPG_ENTITY_META: () => UPG_ENTITY_META,
|
|
99
103
|
UPG_ENTITY_META_BY_ID: () => UPG_ENTITY_META_BY_ID,
|
|
@@ -111,7 +115,9 @@ __export(index_exports, {
|
|
|
111
115
|
UPG_LIFECYCLE_PLANNED_TYPES: () => UPG_LIFECYCLE_PLANNED_TYPES,
|
|
112
116
|
UPG_META_COUNT: () => UPG_META_COUNT,
|
|
113
117
|
UPG_MIGRATIONS: () => UPG_MIGRATIONS,
|
|
118
|
+
UPG_PHASE_COUNT: () => UPG_PHASE_COUNT,
|
|
114
119
|
UPG_PLAYBOOKS: () => UPG_PLAYBOOKS,
|
|
120
|
+
UPG_POLYMORPHIC_EDGE_FAMILIES: () => UPG_POLYMORPHIC_EDGE_FAMILIES,
|
|
115
121
|
UPG_POLYMORPHIC_EDGE_KEYS: () => UPG_POLYMORPHIC_EDGE_KEYS,
|
|
116
122
|
UPG_PORTFOLIO_KINDS: () => UPG_PORTFOLIO_KINDS,
|
|
117
123
|
UPG_PORTFOLIO_SHARED_TYPES: () => UPG_PORTFOLIO_SHARED_TYPES,
|
|
@@ -193,6 +199,7 @@ __export(index_exports, {
|
|
|
193
199
|
getDomainIdForType: () => getDomainIdForType,
|
|
194
200
|
getDomainsInRing: () => getDomainsInRing,
|
|
195
201
|
getEdgePropertySchema: () => getEdgePropertySchema,
|
|
202
|
+
getEntityDescription: () => getEntityDescription,
|
|
196
203
|
getEntityModifierSummary: () => getEntityModifierSummary,
|
|
197
204
|
getEnumScale: () => getEnumScale,
|
|
198
205
|
getEnumValueMeta: () => getEnumValueMeta,
|
|
@@ -1487,6 +1494,19 @@ var UPG_EDGE_CATALOG = {
|
|
|
1487
1494
|
product_contains_research_study: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "product", target_type: "research_study" },
|
|
1488
1495
|
research_study_enrolls_participant: { forward_verb: "enrolls", reverse_verb: "enrolled_in", classification: "hierarchy", source_type: "research_study", target_type: "participant" },
|
|
1489
1496
|
research_study_captures_observation: { forward_verb: "captures", reverse_verb: "captured_in", classification: "hierarchy", source_type: "research_study", target_type: "observation" },
|
|
1497
|
+
// (0.35.0) Provenance for a verbatim quote: which study produced it. The
|
|
1498
|
+
// quote card wanted an outbound `quote_from_research_study`; canon answers it
|
|
1499
|
+
// study-first, matching the whole `research_study_*` family (enrolls ·
|
|
1500
|
+
// captures · clusters_into · produces · investigates · follows · collects),
|
|
1501
|
+
// so the quote renders it INBOUND on its connections rail. The `captures`
|
|
1502
|
+
// verb is deliberately reused from the observation twin above: the F3
|
|
1503
|
+
// duplicate gate is on the (source, target) pair, which is new, and a study
|
|
1504
|
+
// captures raw material by one verb regardless of which raw shape it is.
|
|
1505
|
+
// Companion: `UPG_VALID_CHILDREN.research_study` gains 'quote'. A quote may
|
|
1506
|
+
// now hang from observation (raw-capture parent), insight (synthesis parent)
|
|
1507
|
+
// or research_study (provenance parent) — multi-parent grammar is explicitly
|
|
1508
|
+
// sanctioned, and per-INSTANCE parentage stays single via `parent_id`.
|
|
1509
|
+
research_study_captures_quote: { forward_verb: "captures", reverse_verb: "captured_in", classification: "hierarchy", source_type: "research_study", target_type: "quote" },
|
|
1490
1510
|
research_study_clusters_into_affinity_cluster: { forward_verb: "clusters_into", reverse_verb: "clustered_from", classification: "hierarchy", source_type: "research_study", target_type: "affinity_cluster" },
|
|
1491
1511
|
research_study_produces_insight: { forward_verb: "produces", reverse_verb: "produced_by", classification: "hierarchy", source_type: "research_study", target_type: "insight" },
|
|
1492
1512
|
research_study_investigates_research_question: { forward_verb: "investigates", reverse_verb: "investigated_by", classification: "hierarchy", source_type: "research_study", target_type: "research_question" },
|
|
@@ -1768,6 +1788,22 @@ var UPG_EDGE_CATALOG = {
|
|
|
1768
1788
|
// the work into `task`; v0.7.0/UPG-571 re-canonicalised the statement
|
|
1769
1789
|
// story_statement → user_story; see UPG_EDGE_MIGRATIONS['0.7.0'].)
|
|
1770
1790
|
epic_specified_by_user_story: { forward_verb: "specified_by", reverse_verb: "specifies", classification: "hierarchy", source_type: "epic", target_type: "user_story" },
|
|
1791
|
+
// (0.35.0) The feature-level rung of the same ladder. `epic` is OPTIONAL in
|
|
1792
|
+
// UPG — `feature_decomposed_into_epic` is not mandatory — so a feature that
|
|
1793
|
+
// skips the epic level had no way to reach the stories that specify it, and
|
|
1794
|
+
// `user_story` had exactly one declared parent. Verb pair is cloned verbatim
|
|
1795
|
+
// from the epic rung above so the two rungs read identically; it is NOT
|
|
1796
|
+
// `groups`, which would invent a second verb for one relationship.
|
|
1797
|
+
//
|
|
1798
|
+
// This is also the reason `feature_contains_acceptance_criterion` is NOT
|
|
1799
|
+
// here: with this rung the path is feature → user_story → acceptance_
|
|
1800
|
+
// criterion, and a direct containment edge would be the "X_contains_Y verb
|
|
1801
|
+
// that duplicates the parent link" ARCHITECTURE.md forbids.
|
|
1802
|
+
//
|
|
1803
|
+
// Companion: `UPG_VALID_CHILDREN.feature` gains 'user_story'. Per-instance
|
|
1804
|
+
// parentage stays single (`parent_id`), so the dual declared parent is a
|
|
1805
|
+
// grammar widening, not a dual-parent instance.
|
|
1806
|
+
feature_specified_by_user_story: { forward_verb: "specified_by", reverse_verb: "specifies", classification: "hierarchy", source_type: "feature", target_type: "user_story" },
|
|
1771
1807
|
user_story_verified_by_acceptance_criterion: { forward_verb: "verified_by", reverse_verb: "verifies", classification: "hierarchy", source_type: "user_story", target_type: "acceptance_criterion" },
|
|
1772
1808
|
task_implements_user_story: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "cross-domain", source_type: "task", target_type: "user_story" },
|
|
1773
1809
|
feature_affected_by_bug: { forward_verb: "affected_by", reverse_verb: "affects", classification: "hierarchy", source_type: "feature", target_type: "bug" },
|
|
@@ -2685,6 +2721,51 @@ var UPG_EDGE_CATALOG = {
|
|
|
2685
2721
|
// evidence co-reside), so NOT cross_product_eligible.
|
|
2686
2722
|
document_contains_quote: { forward_verb: "contains", reverse_verb: "contained_in", classification: "semantic", source_type: "document", target_type: "quote" },
|
|
2687
2723
|
document_contains_observation: { forward_verb: "contains", reverse_verb: "contained_in", classification: "semantic", source_type: "document", target_type: "observation" },
|
|
2724
|
+
/**
|
|
2725
|
+
* A document EMBEDS this node's live value at a position in its prose.
|
|
2726
|
+
*
|
|
2727
|
+
* @remarks
|
|
2728
|
+
* NOT `describes`. `document_describes_persona` says the document is ABOUT the
|
|
2729
|
+
* persona. Transclusion says the document RENDERS that node where the prose
|
|
2730
|
+
* sits, so the value a reader sees is the node's current value rather than a
|
|
2731
|
+
* copy taken when the sentence was written. A PRD that transcludes a metric is
|
|
2732
|
+
* not "about" the metric, and collapsing the two would lose the only property
|
|
2733
|
+
* transclusion is bought for: current by construction. The nine-member
|
|
2734
|
+
* `document_describes_*` family is untouched and keeps its meaning.
|
|
2735
|
+
*
|
|
2736
|
+
* POLYMORPHIC because the target set is genuinely open. Anything renderable can
|
|
2737
|
+
* be embedded, and the nine enumerated describe-targets are none of `metric`,
|
|
2738
|
+
* `research_study`, `specification` or `architecture_decision`, which is what
|
|
2739
|
+
* 65% of measured documents carrying zero outbound edges were reaching for.
|
|
2740
|
+
*
|
|
2741
|
+
* THE ANCHOR BINDING (normative). `@unified-product-graph/markdown` already
|
|
2742
|
+
* parses an inline reference form and records the source line of every
|
|
2743
|
+
* occurrence: `[[type:id]]`, `[[type:id|label]]`, `[[type:id|k:v|...]]`,
|
|
2744
|
+
* `[[+type:id]]` for creation, and `[[type:id@product]]` across products. The
|
|
2745
|
+
* grammar is published as Appendix F of the specification paper. The rule that
|
|
2746
|
+
* was missing, and is stated here: an anchor appearing in the body of a
|
|
2747
|
+
* `document` IS a transclusion anchor, and a conformant parser WRITES this edge
|
|
2748
|
+
* beside it. Anchor and edge are one fact recorded twice, so they must be
|
|
2749
|
+
* written together or they drift.
|
|
2750
|
+
*
|
|
2751
|
+
* NO POSITION PROPERTY, deliberately. The obvious `anchor_line` is the most
|
|
2752
|
+
* volatile value a text document has: every insertion above moves every anchor
|
|
2753
|
+
* below, so a stored line number is wrong after the next paragraph and nothing
|
|
2754
|
+
* reports the drift. The anchor IS the position, and it lives in the prose
|
|
2755
|
+
* where ordinary editing moves it for free. What the law asks is that the edge
|
|
2756
|
+
* be written BESIDE the anchor, which is a write-time discipline rather than a
|
|
2757
|
+
* stored field.
|
|
2758
|
+
*
|
|
2759
|
+
* `deliberate_only`, and the 0.33.0 trap is why that is safe here. Flagging a
|
|
2760
|
+
* widened edge `deliberate_only` silently switched five adapters off in the
|
|
2761
|
+
* last release, two of them under a green suite. The rule that came out of it:
|
|
2762
|
+
* an adapter reading an explicit field the SOURCE stores carries an authored
|
|
2763
|
+
* fact rather than an inferred one and must emit deliberate-only edges
|
|
2764
|
+
* EXPLICITLY. A `[[type:id]]` anchor is as explicit as a source gets, because a
|
|
2765
|
+
* person typed it, so the markdown emitter keys on the ANCHOR and never on the
|
|
2766
|
+
* `node` wildcard. No generic pair-resolution path may ever produce one.
|
|
2767
|
+
*/
|
|
2768
|
+
document_transcludes_node: { forward_verb: "transcludes", reverse_verb: "transcluded_in", classification: "semantic", source_type: "document", target_type: "node", deliberate_only: true },
|
|
2688
2769
|
// 5.3 Customer Education & Training Domain
|
|
2689
2770
|
product_educates_via_education_program: { forward_verb: "educates_via", reverse_verb: "educates", classification: "hierarchy", source_type: "product", target_type: "education_program" },
|
|
2690
2771
|
education_program_teaches_via_tutorial: { forward_verb: "teaches_via", reverse_verb: "teaches", classification: "hierarchy", source_type: "education_program", target_type: "tutorial" },
|
|
@@ -2750,10 +2831,10 @@ var UPG_EDGE_CATALOG = {
|
|
|
2750
2831
|
// and G2b is not engaged.
|
|
2751
2832
|
// Membership is UNQUALIFIED: no role property on the edge. `role` is already an
|
|
2752
2833
|
// entity type with team_staffed_with_role, so a role string here would be a
|
|
2753
|
-
// second role model beside a node type. The consequence
|
|
2754
|
-
//
|
|
2755
|
-
//
|
|
2756
|
-
//
|
|
2834
|
+
// second role model beside a node type. The consequence that used to follow is
|
|
2835
|
+
// now closed rather than accepted: the graph can say a team has a slot, that a
|
|
2836
|
+
// person is on the team, AND that the person fills the slot, the last through
|
|
2837
|
+
// `person_holds_role` (0.34.0) rather than through a qualifier here.
|
|
2757
2838
|
//
|
|
2758
2839
|
// Neither is cross_product_eligible: `person` is not portfolio_shared, so the
|
|
2759
2840
|
// 0.18.0 both-endpoints gate rejects the flag regardless of any team-scoping
|
|
@@ -2774,6 +2855,41 @@ var UPG_EDGE_CATALOG = {
|
|
|
2774
2855
|
// the person / team / department triangle is decided once rather than three
|
|
2775
2856
|
// times.
|
|
2776
2857
|
person_reports_to_person: { forward_verb: "reports_to", reverse_verb: "has_report", classification: "semantic", source_type: "person", target_type: "person" },
|
|
2858
|
+
// MINTED 0.34.0 on a grammar-versus-catalog inconsistency readable entirely
|
|
2859
|
+
// inside this package, NOT on the field condition the 0.33.0 bank named. That
|
|
2860
|
+
// condition is still unmet and saying so is the honest framing: both org edges
|
|
2861
|
+
// exist in exactly one graph and it is the saturation fixture, whose person set
|
|
2862
|
+
// contains teams mistyped as people. What replaced it is stronger on its own
|
|
2863
|
+
// terms because it is checkable and adoption-independent:
|
|
2864
|
+
//
|
|
2865
|
+
// `role`'s lifecycle terminates at `filled` and `vacant`, and `filled` is
|
|
2866
|
+
// described "Role is staffed." `team_staffed_with_role` runs team -> slot.
|
|
2867
|
+
// `person_member_of_team` runs person -> team. NOTHING connected a person to
|
|
2868
|
+
// a role. So a graph could legally mark a role `filled` and no edge in the
|
|
2869
|
+
// catalog could say by whom.
|
|
2870
|
+
//
|
|
2871
|
+
// A terminal lifecycle phase that no edge can substantiate is dead schema, and
|
|
2872
|
+
// it is worse than a phantom edge name: a phantom is inert, while `filled` is a
|
|
2873
|
+
// value graphs will actually carry. Same class as the anti-pattern entry that
|
|
2874
|
+
// instructed users toward an edge the catalog refused, which is what minted
|
|
2875
|
+
// `person_reports_to_person` one release ago.
|
|
2876
|
+
//
|
|
2877
|
+
// `semantic`, not `cross-domain`. `person`, `role` and `team` are one domain
|
|
2878
|
+
// (domains.ts, team_org), and T1.7 forbids new same-domain cross-domain edges.
|
|
2879
|
+
// This is the third member of that triangle and it is decided on exactly the
|
|
2880
|
+
// ground the other two were decided on mid-build at 0.33.0.
|
|
2881
|
+
//
|
|
2882
|
+
// Direction is person -> role, matching both 0.33.0 person edges, so the
|
|
2883
|
+
// containment reading stays UNAVAILABLE rather than merely unchosen and
|
|
2884
|
+
// UPG_VALID_CHILDREN is untouched. `role_filled_by_person` would source the
|
|
2885
|
+
// edge on the slot, which reads as the slot owning the person.
|
|
2886
|
+
//
|
|
2887
|
+
// Unqualified, for the reason the membership edge above gives: `role` is a type,
|
|
2888
|
+
// not a string, so a qualifier here would be a second role model.
|
|
2889
|
+
//
|
|
2890
|
+
// Not cross_product_eligible: `person` is not portfolio_shared, so the 0.18.0
|
|
2891
|
+
// both-endpoints gate rejects the flag regardless.
|
|
2892
|
+
person_holds_role: { forward_verb: "holds", reverse_verb: "held_by", classification: "semantic", source_type: "person", target_type: "role" },
|
|
2777
2893
|
team_staffed_with_role: { forward_verb: "staffed_with", reverse_verb: "staffed_in", classification: "hierarchy", source_type: "team", target_type: "role" },
|
|
2778
2894
|
team_targets_team_okr: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "hierarchy", source_type: "team", target_type: "team_okr" },
|
|
2779
2895
|
team_reflects_in_retrospective: { forward_verb: "reflects_in", reverse_verb: "reflects", classification: "hierarchy", source_type: "team", target_type: "retrospective" },
|
|
@@ -2784,6 +2900,16 @@ var UPG_EDGE_CATALOG = {
|
|
|
2784
2900
|
team_decides_decision: { forward_verb: "decides", reverse_verb: "decided_by", classification: "hierarchy", source_type: "team", target_type: "decision" },
|
|
2785
2901
|
decision_references_decision: { forward_verb: "references", reverse_verb: "referenced_by", classification: "cross-domain", source_type: "decision", target_type: "decision" },
|
|
2786
2902
|
stakeholder_maps_to_persona: { forward_verb: "maps_to", reverse_verb: "mapped_by", classification: "cross-domain", source_type: "stakeholder", target_type: "persona" },
|
|
2903
|
+
// (0.35.0) The stakeholder-map relation: who cares about which outcome.
|
|
2904
|
+
// Deliberately a verb-only edge and NOT a `kind: 'scale'` property on a
|
|
2905
|
+
// generic `stakeholder_relates_to_node` — UPG edges are payload-free outside
|
|
2906
|
+
// the gated `carries_properties` set, and the MAGNITUDE of the stake is
|
|
2907
|
+
// already `influence` / `interest` on the stakeholder itself. The reverse
|
|
2908
|
+
// reading is what makes the pair right: "outcome matters_to stakeholder".
|
|
2909
|
+
// Sits beside `persona_pursues_outcome` (the persona twin) and
|
|
2910
|
+
// `product_influenced_by_stakeholder`. Retires the manifest's denormalised
|
|
2911
|
+
// `stake_in` string: the subtitle renders the target outcome's title.
|
|
2912
|
+
stakeholder_invested_in_outcome: { forward_verb: "invested_in", reverse_verb: "matters_to", classification: "cross-domain", source_type: "stakeholder", target_type: "outcome" },
|
|
2787
2913
|
team_okr_aligns_with_objective: { forward_verb: "aligns_with", reverse_verb: "aligned_by", classification: "cross-domain", source_type: "team_okr", target_type: "objective" },
|
|
2788
2914
|
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" },
|
|
2789
2915
|
ceremony_involves_team: { forward_verb: "involves", reverse_verb: "involved_in", classification: "cross-domain", source_type: "ceremony", target_type: "team" },
|
|
@@ -2862,6 +2988,27 @@ var UPG_EDGE_CATALOG = {
|
|
|
2862
2988
|
compliance_requirement_constrains_feature: { forward_verb: "constrains", reverse_verb: "constrained_by", classification: "cross-domain", source_type: "compliance_requirement", target_type: "feature" },
|
|
2863
2989
|
compliance_requirement_constrains_decision: { forward_verb: "constrains", reverse_verb: "constrained_by", classification: "cross-domain", source_type: "compliance_requirement", target_type: "decision" },
|
|
2864
2990
|
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" },
|
|
2991
|
+
// ── Risk exposure (0.35.0) ──────────────────────────────────────────────────
|
|
2992
|
+
// Typed source, wildcard target: the 16th registered polymorphic family, and
|
|
2993
|
+
// structurally the "decision-to-anything" construction with `risk` in the
|
|
2994
|
+
// source slot. Both are `cross-domain`, so neither enters UPG_VALID_CHILDREN
|
|
2995
|
+
// and neither can be walked as containment.
|
|
2996
|
+
//
|
|
2997
|
+
// Why wildcard rather than typed-per-target: what a risk puts at stake is
|
|
2998
|
+
// genuinely unbounded (outcome, key_result, release, service, contract,
|
|
2999
|
+
// launch, metric, feature), and so is what mitigates it (decision, feature,
|
|
3000
|
+
// experiment, security_control, compliance_requirement). Enumerating either
|
|
3001
|
+
// side balloons the catalogue across two open sets while the endpoint carries
|
|
3002
|
+
// no structural role — the exact condition ARCHITECTURE.md admits polymorphism
|
|
3003
|
+
// for. The typed `security_control_mitigates_threat` stays as the
|
|
3004
|
+
// security-domain edge; `risk_mitigated_by_node` is the product generalisation.
|
|
3005
|
+
//
|
|
3006
|
+
// These two replace three manifest fields that were properties pretending to
|
|
3007
|
+
// be relations: `scope_entities` (an entity-reference list) is
|
|
3008
|
+
// risk_threatens_node, and `mitigation_actions` (a top-list of action
|
|
3009
|
+
// strings) is risk_mitigated_by_node. The prose `mitigation?: string` stays.
|
|
3010
|
+
risk_threatens_node: { forward_verb: "threatens", reverse_verb: "threatened_by", classification: "cross-domain", source_type: "risk", target_type: "node" },
|
|
3011
|
+
risk_mitigated_by_node: { forward_verb: "mitigated_by", reverse_verb: "mitigates", classification: "cross-domain", source_type: "risk", target_type: "node" },
|
|
2865
3012
|
data_contract_governs_data_source: { forward_verb: "governs", reverse_verb: "governed_by", classification: "cross-domain", source_type: "data_contract", target_type: "data_source" },
|
|
2866
3013
|
compliance_framework_requires_security_control: { forward_verb: "requires", reverse_verb: "required_by", classification: "cross-domain", source_type: "compliance_framework", target_type: "security_control" },
|
|
2867
3014
|
security_audit_validates_compliance_framework: { forward_verb: "validates", reverse_verb: "validated_by", classification: "cross-domain", source_type: "security_audit", target_type: "compliance_framework" },
|
|
@@ -4144,8 +4291,63 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
|
4144
4291
|
// Project membership (0.33.0, widened from project_delivers_epic):
|
|
4145
4292
|
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
4146
4293
|
// bug}. Same construction and same reasoning as the cadence edge above.
|
|
4147
|
-
"project_delivers_work_item"
|
|
4294
|
+
"project_delivers_work_item",
|
|
4295
|
+
// Document transclusion (0.34.0): a document embeds any renderable node's live
|
|
4296
|
+
// value at a position in its prose. Open by nature rather than by an
|
|
4297
|
+
// enumeration nobody wanted to write out.
|
|
4298
|
+
"document_transcludes_node",
|
|
4299
|
+
// Risk exposure (0.35.0): what a risk threatens, and what mitigates it.
|
|
4300
|
+
// Typed source, wildcard target — the decision-to-anything construction with
|
|
4301
|
+
// `risk` in the source slot. Both cross-domain, so containment is untouched.
|
|
4302
|
+
"risk_threatens_node",
|
|
4303
|
+
"risk_mitigated_by_node"
|
|
4148
4304
|
];
|
|
4305
|
+
var UPG_POLYMORPHIC_EDGE_FAMILIES = {
|
|
4306
|
+
"universal semantic verbs": ["node_informs_node", "node_constrains_node", "node_inspires_node"],
|
|
4307
|
+
"decision-to-anything": ["decision_influences_node", "decision_constrained_by_node", "decision_produces_node"],
|
|
4308
|
+
"universal ownership": [
|
|
4309
|
+
"node_owned_by_team",
|
|
4310
|
+
"node_owned_by_role",
|
|
4311
|
+
"node_owned_by_stakeholder",
|
|
4312
|
+
"node_owned_by_department",
|
|
4313
|
+
"node_owned_by_person"
|
|
4314
|
+
],
|
|
4315
|
+
"universal architecture references": ["node_belongs_to_bounded_context"],
|
|
4316
|
+
"framework exercises": ["framework_exercise_includes_node"],
|
|
4317
|
+
"universal classification": ["node_classified_as_classification_value"],
|
|
4318
|
+
"work-item issue links": [
|
|
4319
|
+
"work_item_blocks_work_item",
|
|
4320
|
+
"work_item_relates_to_work_item",
|
|
4321
|
+
"work_item_duplicates_work_item"
|
|
4322
|
+
],
|
|
4323
|
+
"workspace provenance": ["workspace_produced_node"],
|
|
4324
|
+
"canvas arrangement and published-view focus": ["workspace_arranges_node", "composition_focuses_node"],
|
|
4325
|
+
"benchmark subject": ["eval_benchmark_measures_node"],
|
|
4326
|
+
// 0.32.0. Assignment is its OWN family and deliberately not a member of
|
|
4327
|
+
// 'universal ownership': keeping them apart in this partition is the same
|
|
4328
|
+
// ruling the edge itself makes, so a future author who merges them here is
|
|
4329
|
+
// making that decision visibly rather than by tidying a list.
|
|
4330
|
+
"universal assignment": ["node_assigned_to_person"],
|
|
4331
|
+
"capture subject": ["capture_renders_node"],
|
|
4332
|
+
"cadence scheduling": ["planning_cycle_schedules_work_item"],
|
|
4333
|
+
// 0.33.0. Project membership is its own family and deliberately not a member
|
|
4334
|
+
// of 'cadence scheduling', even though both range over the same work-item set:
|
|
4335
|
+
// a cycle SCHEDULES work in time and a project REFERENCES the work it delivers,
|
|
4336
|
+
// and merging them here would quietly assert those are one relation.
|
|
4337
|
+
"project membership": ["project_delivers_work_item"],
|
|
4338
|
+
// 0.34.0. Its own family and deliberately not merged with the nine-member
|
|
4339
|
+
// `document_describes_*` set, which is not polymorphic at all: those are
|
|
4340
|
+
// enumerated edges saying a document is ABOUT a thing. Transclusion says it
|
|
4341
|
+
// RENDERS the thing, and the difference is the whole reason the edge exists.
|
|
4342
|
+
"document transclusion": ["document_transcludes_node"],
|
|
4343
|
+
// 0.35.0. Its own family and deliberately NOT merged into
|
|
4344
|
+
// 'decision-to-anything', which it mirrors structurally. The two say different
|
|
4345
|
+
// things: a decision INFLUENCES what it touches, a risk ENDANGERS what it
|
|
4346
|
+
// threatens, and a partition that merged them on shape alone would assert
|
|
4347
|
+
// those are one relation. Two members because exposure has two directions,
|
|
4348
|
+
// and collapsing them would lose which way an edge points.
|
|
4349
|
+
"risk exposure": ["risk_threatens_node", "risk_mitigated_by_node"]
|
|
4350
|
+
};
|
|
4149
4351
|
var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
|
|
4150
4352
|
function isPolymorphicEdge(key) {
|
|
4151
4353
|
const def = UPG_EDGE_CATALOG[key];
|
|
@@ -4163,1273 +4365,132 @@ function getEdgePropertySchema(type) {
|
|
|
4163
4365
|
return def?.property_schema;
|
|
4164
4366
|
}
|
|
4165
4367
|
|
|
4166
|
-
// src/
|
|
4167
|
-
var
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4368
|
+
// src/grammar/lifecycles.ts
|
|
4369
|
+
var PRODUCT_LIFECYCLE = {
|
|
4370
|
+
entity_type: "product",
|
|
4371
|
+
initial_phase: "concept",
|
|
4372
|
+
terminal_phases: ["sunset"],
|
|
4373
|
+
phases: [
|
|
4374
|
+
{
|
|
4375
|
+
id: "concept",
|
|
4376
|
+
status_category: "triage",
|
|
4377
|
+
label: "Concept",
|
|
4378
|
+
description: "Napkin idea. Problem shape and solution sketch, pre-validation.",
|
|
4379
|
+
transitions_to: ["validation", "build", "beta", "launch", "growth", "mature", "sunset"]
|
|
4380
|
+
},
|
|
4381
|
+
{
|
|
4382
|
+
id: "validation",
|
|
4383
|
+
status_category: "started",
|
|
4384
|
+
label: "Validation",
|
|
4385
|
+
description: "Testing demand. User conversations and experiments pressure-test assumptions before build commits.",
|
|
4386
|
+
transitions_to: ["build", "beta", "launch", "growth", "mature", "sunset"]
|
|
4387
|
+
},
|
|
4388
|
+
{
|
|
4389
|
+
id: "build",
|
|
4390
|
+
status_category: "started",
|
|
4391
|
+
label: "Build",
|
|
4392
|
+
description: "Actively developing v1. Core functionality is being authored, pre-user.",
|
|
4393
|
+
transitions_to: ["beta", "launch", "growth", "mature", "sunset"]
|
|
4394
|
+
},
|
|
4395
|
+
{
|
|
4396
|
+
id: "beta",
|
|
4397
|
+
status_category: "started",
|
|
4398
|
+
label: "Beta",
|
|
4399
|
+
description: "Early users, iterating. Feature-complete enough to learn from, still changing weekly.",
|
|
4400
|
+
transitions_to: ["launch", "growth", "mature", "maintenance", "sunset"]
|
|
4401
|
+
},
|
|
4402
|
+
{
|
|
4403
|
+
id: "launch",
|
|
4404
|
+
status_category: "started",
|
|
4405
|
+
label: "Launch",
|
|
4406
|
+
description: "Generally available. Announced to the target audience, open past beta access.",
|
|
4407
|
+
transitions_to: ["growth", "mature", "maintenance", "sunset"]
|
|
4408
|
+
},
|
|
4409
|
+
{
|
|
4410
|
+
id: "growth",
|
|
4411
|
+
status_category: "started",
|
|
4412
|
+
label: "Growth",
|
|
4413
|
+
description: "Scaling users and revenue. Product fit validated; focus is acquisition, retention, expansion.",
|
|
4414
|
+
transitions_to: ["mature", "maintenance", "sunset"]
|
|
4415
|
+
},
|
|
4416
|
+
{
|
|
4417
|
+
id: "mature",
|
|
4418
|
+
status_category: "started",
|
|
4419
|
+
label: "Mature",
|
|
4420
|
+
description: "Stable. Growth levelled; investment weights toward optimisation, efficiency, and durability.",
|
|
4421
|
+
transitions_to: ["maintenance", "sunset"]
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
id: "maintenance",
|
|
4425
|
+
status_category: "started",
|
|
4426
|
+
label: "Maintenance",
|
|
4427
|
+
description: "Sustaining. Minimal investment keeps the product running for existing users. Can transition back to `mature` on a strategic revival.",
|
|
4428
|
+
transitions_to: ["mature", "sunset"]
|
|
4429
|
+
},
|
|
4430
|
+
{
|
|
4431
|
+
id: "sunset",
|
|
4432
|
+
status_category: "completed",
|
|
4433
|
+
label: "Sunset",
|
|
4434
|
+
description: "Winding down or retired. Existing users are being migrated or offboarded.",
|
|
4435
|
+
transitions_to: []
|
|
4436
|
+
}
|
|
4437
|
+
]
|
|
4208
4438
|
};
|
|
4209
|
-
var
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
]
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
"hosts",
|
|
4237
|
-
// host runs the hosted product inside itself (container -> contained)
|
|
4238
|
-
// OKR / measurement rollup primitives: the cascade ACROSS products (subordinate
|
|
4239
|
-
// -> superior). `contributes_to` rolls a product strategy entity up to a higher-
|
|
4240
|
-
// level one; `rolls_up_to` feeds a product metric into a higher-level north-star.
|
|
4241
|
-
"contributes_to",
|
|
4242
|
-
"rolls_up_to",
|
|
4243
|
-
// Canonical instance + area-to-audience: the target lives in the portfolio
|
|
4244
|
-
// `registry` section (`registry/{node_id}`). `instance_of` is same-type
|
|
4245
|
-
// canonical-to-instance; the area edges are minted via `link_area_to_audience`.
|
|
4246
|
-
"instance_of",
|
|
4247
|
-
"area_serves_persona",
|
|
4248
|
-
"area_targets_market_segment",
|
|
4249
|
-
// Foundations (0.9.12): a product-graph entity links to a canonical specification
|
|
4250
|
-
// or primitive in the registry (`registry/{node_id}` target, same shape as
|
|
4251
|
-
// instance_of). Registry-internal spec-to-spec links are catalog edges, not these.
|
|
4252
|
-
"product_implements_specification",
|
|
4253
|
-
"product_exposes_specification",
|
|
4254
|
-
"feature_conforms_to_specification",
|
|
4255
|
-
"api_contract_speaks_specification",
|
|
4256
|
-
"product_exposes_primitive",
|
|
4257
|
-
"feature_manipulates_primitive",
|
|
4258
|
-
"primitive_stored_as_data_type"
|
|
4259
|
-
];
|
|
4260
|
-
var UPG_CROSS_EDGE_TYPES = [
|
|
4261
|
-
...UPG_CROSS_ONLY_EDGE_TYPES,
|
|
4262
|
-
...UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES
|
|
4263
|
-
];
|
|
4264
|
-
var REGISTRY_PRODUCT_ID = "registry";
|
|
4265
|
-
var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
|
|
4266
|
-
|
|
4267
|
-
// src/registry/entity-type-resolution.ts
|
|
4268
|
-
var TYPES = getTypes();
|
|
4269
|
-
var TYPES_SET = new Set(TYPES);
|
|
4270
|
-
var UnknownEntityTypeError = class extends Error {
|
|
4271
|
-
suggestions;
|
|
4272
|
-
rawType;
|
|
4273
|
-
constructor(rawType, suggestions) {
|
|
4274
|
-
const suffix = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
4275
|
-
super(`Unknown entity type: "${rawType}".${suffix}`);
|
|
4276
|
-
this.name = "UnknownEntityTypeError";
|
|
4277
|
-
this.rawType = rawType;
|
|
4278
|
-
this.suggestions = suggestions;
|
|
4279
|
-
}
|
|
4439
|
+
var NEED_LIFECYCLE = {
|
|
4440
|
+
entity_type: "need",
|
|
4441
|
+
initial_phase: "raw",
|
|
4442
|
+
terminal_phases: ["prioritized"],
|
|
4443
|
+
phases: [
|
|
4444
|
+
{
|
|
4445
|
+
id: "raw",
|
|
4446
|
+
status_category: "triage",
|
|
4447
|
+
label: "Raw",
|
|
4448
|
+
description: "Captured from an interview, observation, or support ticket. Articulation may be rough; may duplicate an existing need.",
|
|
4449
|
+
transitions_to: ["validated"]
|
|
4450
|
+
},
|
|
4451
|
+
{
|
|
4452
|
+
id: "validated",
|
|
4453
|
+
status_category: "started",
|
|
4454
|
+
label: "Validated",
|
|
4455
|
+
description: "Corroborated by multiple data points or targeted research. The team agrees it is a real, recurring need.",
|
|
4456
|
+
transitions_to: ["prioritized"]
|
|
4457
|
+
},
|
|
4458
|
+
{
|
|
4459
|
+
id: "prioritized",
|
|
4460
|
+
status_category: "completed",
|
|
4461
|
+
label: "Prioritized",
|
|
4462
|
+
description: "Ranked against other needs. Ready to inform opportunity definition and solution exploration.",
|
|
4463
|
+
transitions_to: []
|
|
4464
|
+
}
|
|
4465
|
+
]
|
|
4280
4466
|
};
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4467
|
+
var OPPORTUNITY_LIFECYCLE = {
|
|
4468
|
+
entity_type: "opportunity",
|
|
4469
|
+
initial_phase: "identified",
|
|
4470
|
+
terminal_phases: [],
|
|
4471
|
+
phases: [
|
|
4472
|
+
{
|
|
4473
|
+
id: "identified",
|
|
4474
|
+
status_category: "triage",
|
|
4475
|
+
label: "Identified",
|
|
4476
|
+
description: "A problem worth solving has been named. Sources include needs, insights, market trends, or competitive signals. Sizing and validation come next.",
|
|
4477
|
+
transitions_to: ["validated", "deferred"]
|
|
4478
|
+
},
|
|
4479
|
+
{
|
|
4480
|
+
id: "validated",
|
|
4481
|
+
status_category: "started",
|
|
4482
|
+
label: "Validated",
|
|
4483
|
+
description: "Research and evidence support pursuit. The problem is real, frequent, and painful enough to justify investment.",
|
|
4484
|
+
transitions_to: ["deferred"]
|
|
4485
|
+
},
|
|
4486
|
+
{
|
|
4487
|
+
id: "deferred",
|
|
4488
|
+
status_category: "backlog",
|
|
4489
|
+
label: "Deferred",
|
|
4490
|
+
description: "Acknowledged but parked. Capacity, priority, or strategic timing reasons. Can be reopened.",
|
|
4491
|
+
transitions_to: ["identified"]
|
|
4301
4492
|
}
|
|
4302
|
-
|
|
4303
|
-
if (i < al || j < bl) edits++;
|
|
4304
|
-
return edits <= 1;
|
|
4305
|
-
}
|
|
4306
|
-
function resolveEntityType(rawType) {
|
|
4307
|
-
if (typeof rawType !== "string" || rawType.length === 0) {
|
|
4308
|
-
throw new UnknownEntityTypeError(String(rawType ?? ""), []);
|
|
4309
|
-
}
|
|
4310
|
-
if (TYPES_SET.has(rawType)) {
|
|
4311
|
-
return { canonical: rawType };
|
|
4312
|
-
}
|
|
4313
|
-
const replacement = getReplacementType(rawType);
|
|
4314
|
-
if (replacement && TYPES_SET.has(replacement)) {
|
|
4315
|
-
return { canonical: replacement, alias: { from: rawType, to: replacement } };
|
|
4316
|
-
}
|
|
4317
|
-
const suggestions = [];
|
|
4318
|
-
for (const t of TYPES) {
|
|
4319
|
-
if (withinEditDistance1(t, rawType)) suggestions.push(t);
|
|
4320
|
-
if (suggestions.length >= 5) break;
|
|
4321
|
-
}
|
|
4322
|
-
throw new UnknownEntityTypeError(rawType, suggestions);
|
|
4323
|
-
}
|
|
4324
|
-
|
|
4325
|
-
// src/grammar/hierarchy.ts
|
|
4326
|
-
var UPG_VALID_CHILDREN = {
|
|
4327
|
-
// ── Foundations (0.9.12): registry canonicals that self-nest. A
|
|
4328
|
-
// specification extends a specification (a dialect of a parent spec); a
|
|
4329
|
-
// primitive composes primitives (a block contains spans). These back the
|
|
4330
|
-
// hierarchy-classified specification_extends_specification /
|
|
4331
|
-
// primitive_composes_primitive catalog edges.
|
|
4332
|
-
specification: ["specification"],
|
|
4333
|
-
primitive: ["primitive"],
|
|
4334
|
-
// An operating_lifecycle (e.g. the content-ops lifecycle) contains its ordered
|
|
4335
|
-
// operating_stage children. Backs the operating_lifecycle_contains_operating_stage edge.
|
|
4336
|
-
operating_lifecycle: ["operating_stage"],
|
|
4337
|
-
// ── Product (root) ─────────────────────────────────────────────────────────
|
|
4338
|
-
product: [
|
|
4339
|
-
// Strategy
|
|
4340
|
-
"vision",
|
|
4341
|
-
"outcome",
|
|
4342
|
-
"objective",
|
|
4343
|
-
"metric",
|
|
4344
|
-
"decision",
|
|
4345
|
-
"constraint",
|
|
4346
|
-
// User
|
|
4347
|
-
"persona",
|
|
4348
|
-
// Discovery (via outcome children)
|
|
4349
|
-
// Validation (via hypothesis children)
|
|
4350
|
-
// Market Intelligence
|
|
4351
|
-
"competitor",
|
|
4352
|
-
"market_trend",
|
|
4353
|
-
"market_segment",
|
|
4354
|
-
"competitive_analysis",
|
|
4355
|
-
// User Research
|
|
4356
|
-
"research_study",
|
|
4357
|
-
// Experience Design
|
|
4358
|
-
"user_journey",
|
|
4359
|
-
"user_flow",
|
|
4360
|
-
"wireframe",
|
|
4361
|
-
"screen",
|
|
4362
|
-
// Design System
|
|
4363
|
-
"design_system",
|
|
4364
|
-
"design_component",
|
|
4365
|
-
// Brand Identity
|
|
4366
|
-
"brand_identity",
|
|
4367
|
-
// Product Specification
|
|
4368
|
-
"feature",
|
|
4369
|
-
"feature_area",
|
|
4370
|
-
"release",
|
|
4371
|
-
"roadmap",
|
|
4372
|
-
"roadmap_theme",
|
|
4373
|
-
"planning_cycle",
|
|
4374
|
-
// Configuration (0.30.0): the levers whose values select which surface tree
|
|
4375
|
-
// the product renders. Few per product, and top-level because an axis is a
|
|
4376
|
-
// product-wide fact rather than a child of any one feature or surface.
|
|
4377
|
-
"configuration_axis",
|
|
4378
|
-
// Classification (0.32.0): the product's own taxonomies — the grouped-label
|
|
4379
|
-
// case, one named group over a set of values. Same reasoning as the
|
|
4380
|
-
// configuration axis above, and until now the only parent a
|
|
4381
|
-
// classification_axis could have was a competitive_analysis.
|
|
4382
|
-
"classification_axis",
|
|
4383
|
-
// Engineering
|
|
4384
|
-
"bounded_context",
|
|
4385
|
-
"code_repository",
|
|
4386
|
-
"integration_pattern",
|
|
4387
|
-
"external_api",
|
|
4388
|
-
"data_flow",
|
|
4389
|
-
// Growth
|
|
4390
|
-
"funnel",
|
|
4391
|
-
"acquisition_channel",
|
|
4392
|
-
"cohort",
|
|
4393
|
-
"behavioral_segment",
|
|
4394
|
-
"growth_loop",
|
|
4395
|
-
"attribution_model",
|
|
4396
|
-
// Business Model
|
|
4397
|
-
"business_model",
|
|
4398
|
-
// Go-To-Market
|
|
4399
|
-
"gtm_strategy",
|
|
4400
|
-
// Team & Organisation
|
|
4401
|
-
// `person` is containment-free (see UPG_CONTAINMENT_FREE_TYPES below)
|
|
4402
|
-
// (referenced via edges, not nested under product)
|
|
4403
|
-
"team",
|
|
4404
|
-
"stakeholder",
|
|
4405
|
-
"department",
|
|
4406
|
-
// Data & Analytics
|
|
4407
|
-
"data_source",
|
|
4408
|
-
"event_schema",
|
|
4409
|
-
"dashboard",
|
|
4410
|
-
"data_domain",
|
|
4411
|
-
"glossary_term",
|
|
4412
|
-
// Content & Knowledge
|
|
4413
|
-
"content_piece",
|
|
4414
|
-
"knowledge_base_article",
|
|
4415
|
-
"brand_asset",
|
|
4416
|
-
"document",
|
|
4417
|
-
"documentation_template",
|
|
4418
|
-
// Legal
|
|
4419
|
-
"legal_entity",
|
|
4420
|
-
"privacy_policy",
|
|
4421
|
-
// Compliance
|
|
4422
|
-
"compliance_requirement",
|
|
4423
|
-
"risk",
|
|
4424
|
-
"data_contract",
|
|
4425
|
-
"audit_log_policy",
|
|
4426
|
-
"compliance_framework",
|
|
4427
|
-
// DevOps & Platform
|
|
4428
|
-
"service_level_objective",
|
|
4429
|
-
"incident",
|
|
4430
|
-
"runbook",
|
|
4431
|
-
"monitor",
|
|
4432
|
-
"ci_pipeline",
|
|
4433
|
-
"release_strategy",
|
|
4434
|
-
"on_call_rotation",
|
|
4435
|
-
"infrastructure_component",
|
|
4436
|
-
// Security
|
|
4437
|
-
"threat_model",
|
|
4438
|
-
"security_control",
|
|
4439
|
-
"security_policy",
|
|
4440
|
-
"penetration_test",
|
|
4441
|
-
"security_review",
|
|
4442
|
-
"data_classification",
|
|
4443
|
-
"access_policy",
|
|
4444
|
-
// Accessibility
|
|
4445
|
-
"a11y_standard",
|
|
4446
|
-
"a11y_audit",
|
|
4447
|
-
"a11y_annotation",
|
|
4448
|
-
// Quality Assurance
|
|
4449
|
-
"test_plan",
|
|
4450
|
-
"test_suite",
|
|
4451
|
-
"qa_session",
|
|
4452
|
-
"test_coverage_report",
|
|
4453
|
-
"test_environment",
|
|
4454
|
-
// Customer Feedback
|
|
4455
|
-
"feedback_program",
|
|
4456
|
-
"user_advisory_board",
|
|
4457
|
-
"beta_program",
|
|
4458
|
-
// Pricing & Packaging
|
|
4459
|
-
"pricing_strategy",
|
|
4460
|
-
// AI & Machine Learning
|
|
4461
|
-
"ai_model",
|
|
4462
|
-
"model_comparison",
|
|
4463
|
-
// Workflows & Agents
|
|
4464
|
-
"workflow_template",
|
|
4465
|
-
"agent_definition",
|
|
4466
|
-
// Sales & Revenue
|
|
4467
|
-
"pipeline_sales",
|
|
4468
|
-
"account",
|
|
4469
|
-
"lead",
|
|
4470
|
-
"subscription",
|
|
4471
|
-
"forecast",
|
|
4472
|
-
// Program Management
|
|
4473
|
-
// `milestone` is a SECOND, shallower parent here (Portfolio Phase 2,
|
|
4474
|
-
// 2026-08-14), alongside its existing `project` parent: a product can own a
|
|
4475
|
-
// milestone outright with no program/project above it. Required by the
|
|
4476
|
-
// hierarchy-orphan guard now that `product_targets_milestone` exists.
|
|
4477
|
-
"program",
|
|
4478
|
-
"milestone",
|
|
4479
|
-
// Marketing
|
|
4480
|
-
"marketing_strategy",
|
|
4481
|
-
"press_release",
|
|
4482
|
-
"event",
|
|
4483
|
-
"community_initiative",
|
|
4484
|
-
// Customer Success
|
|
4485
|
-
"support_ticket",
|
|
4486
|
-
"customer_feedback",
|
|
4487
|
-
"churn_reason",
|
|
4488
|
-
"customer_health_score",
|
|
4489
|
-
"playbook",
|
|
4490
|
-
"service_level_agreement",
|
|
4491
|
-
"success_milestone",
|
|
4492
|
-
"service_blueprint",
|
|
4493
|
-
"nps_campaign",
|
|
4494
|
-
// Localisation
|
|
4495
|
-
"locale",
|
|
4496
|
-
"locale_config",
|
|
4497
|
-
// Customer Education
|
|
4498
|
-
"education_program",
|
|
4499
|
-
// Partners & Ecosystem
|
|
4500
|
-
"partner_program",
|
|
4501
|
-
"api_ecosystem",
|
|
4502
|
-
"developer_portal",
|
|
4503
|
-
// Portfolio
|
|
4504
|
-
// (UPG-677) product no longer lists product_area as a child: the
|
|
4505
|
-
// product↔product_area containment is the single direction
|
|
4506
|
-
// product_area_contains_product (product_area → product). The inverse
|
|
4507
|
-
// product_categorised_in_product_area edge was collapsed.
|
|
4508
|
-
// Workspace
|
|
4509
|
-
"workspace"
|
|
4510
|
-
],
|
|
4511
|
-
// ── Metric & Outcome hierarchy ──────────────────────────────────────────────
|
|
4512
|
-
outcome: ["metric", "opportunity", "feature"],
|
|
4513
|
-
// ── User & Jobs hierarchy ───────────────────────────────────────────────────
|
|
4514
|
-
persona: ["job", "need", "switching_cost", "desired_outcome"],
|
|
4515
|
-
job: ["need", "desired_outcome", "job_step"],
|
|
4516
|
-
// ── Discovery hierarchy ─────────────────────────────────────────────────────
|
|
4517
|
-
opportunity: ["solution", "design_concept", "feasibility_study", "design_sprint"],
|
|
4518
|
-
// solutions now propose hypothesis (the templated
|
|
4519
|
-
// belief), not the legacy hypothesis type.
|
|
4520
|
-
solution: ["hypothesis", "prototype", "metric"],
|
|
4521
|
-
// ── Validation hierarchy ────────────────────────────────────────────────────
|
|
4522
|
-
// (UPG-664) The validation chain is a single-parent line:
|
|
4523
|
-
// hypothesis ▷ experiment_plan ▷ experiment ▷ experiment_run.
|
|
4524
|
-
// hypothesis is canonical (hypothesis_evidence collapsed into evidence;
|
|
4525
|
-
// relationship expressed via hypothesis_has_evidence edge; evidence.direction
|
|
4526
|
-
// carries supports/refutes/neutral semantics). `test_plan` re-homed to the
|
|
4527
|
-
// QA/testing domain (UPG-678), so it is no longer a hypothesis child.
|
|
4528
|
-
hypothesis: ["experiment_plan", "research_plan", "evidence"],
|
|
4529
|
-
// experiment_plan is the canonical validation PLAN (graduated stable,
|
|
4530
|
-
// UPG-664). It designs the experiment it produces; the plan owns the
|
|
4531
|
-
// experiment in the containment line. It also retains experiment_run as a
|
|
4532
|
-
// child for the v0.2.6 split-migration path
|
|
4533
|
-
// (`experiment_plan_ran_as_experiment_run`), where a legacy experiment routes
|
|
4534
|
-
// directly to a plan + run pair.
|
|
4535
|
-
experiment_plan: ["experiment", "experiment_run"],
|
|
4536
|
-
// experiment is the canonical structured test. It owns its run(s) and the
|
|
4537
|
-
// learning/evidence it produces. The optional experiment_run child captures
|
|
4538
|
-
// the multi-run / replication case.
|
|
4539
|
-
experiment: ["experiment_run", "learning", "evidence"],
|
|
4540
|
-
// experiment_run carries learning/evidence/metric children (run produces
|
|
4541
|
-
// evidence) and self-nests for replications/multi-armed runs (V9: this is
|
|
4542
|
-
// where self-nesting genuinely lives, not on `experiment`).
|
|
4543
|
-
experiment_run: ["learning", "evidence", "metric", "experiment_run"],
|
|
4544
|
-
// ── OKR hierarchy ──────────────────────────────────────────────────────────
|
|
4545
|
-
// strategic_question (0.17.4): an open coordination question raised under an
|
|
4546
|
-
// objective (objective_raises_strategic_question, hierarchy).
|
|
4547
|
-
objective: ["key_result", "metric", "strategic_question"],
|
|
4548
|
-
key_result: ["metric"],
|
|
4549
|
-
// ── Strategic cascade: vision → mission → strategic_pillar → strategic_theme →
|
|
4550
|
-
// { initiative, objective } ───────────────────────────────────────────────
|
|
4551
|
-
//
|
|
4552
|
-
// strategic_pillar vs strategic_theme (UPG-692 T3.3 — distinct cascade levels,
|
|
4553
|
-
// NOT duplicates; sharpened rather than merged):
|
|
4554
|
-
// - strategic_pillar = a DURABLE structural division of the strategy. A
|
|
4555
|
-
// standing area the org organises around for years (e.g. "Structured
|
|
4556
|
-
// content", "Developer experience"). Few, long-lived; time_horizon is
|
|
4557
|
-
// multi-year / open-ended; carries its own `success_indicator`.
|
|
4558
|
-
// - strategic_theme = a TIME-BOUND thrust WITHIN a pillar. The current bet
|
|
4559
|
-
// for a period (e.g. "Go AI-native this year"). More numerous, rotates;
|
|
4560
|
-
// time_horizon is bounded (Q / FY); has NO `success_indicator` because it
|
|
4561
|
-
// is measured through its child objectives (strategic_theme_contains_objective).
|
|
4562
|
-
// That success-measurement asymmetry is the load-bearing distinction.
|
|
4563
|
-
vision: ["mission"],
|
|
4564
|
-
mission: ["strategic_pillar"],
|
|
4565
|
-
// 0.20.1: metric added as a strategic_pillar child, backing
|
|
4566
|
-
// strategic_pillar_measured_by_metric (hierarchy) — the pillar's own
|
|
4567
|
-
// north-star, mirrored from objective: ['key_result', 'metric', ...] one
|
|
4568
|
-
// level down the cascade.
|
|
4569
|
-
strategic_pillar: ["strategic_theme", "capability", "value_stream", "decision", "metric"],
|
|
4570
|
-
// v0.5.4 (UPG-511): objectives are the specific quarterly bets *within* a
|
|
4571
|
-
// strategic theme. The theme is the multi-quarter focus area; objectives are
|
|
4572
|
-
// subordinate. Pairs with `strategic_theme_contains_objective` in the edge
|
|
4573
|
-
// catalog (hierarchy, strategic_theme → objective, reverse_verb: rolls_up_to;
|
|
4574
|
-
// renamed from objective_rolls_up_to_strategic_theme in UPG-676).
|
|
4575
|
-
strategic_theme: ["initiative", "objective"],
|
|
4576
|
-
initiative: ["assumption", "strategic_question"],
|
|
4577
|
-
// v0.5.2 (UPG-528): capability decomposes into sub-capabilities (Wardley
|
|
4578
|
-
// value-chain spine) and is realised by user-facing features. Pairs with
|
|
4579
|
-
// `capability_depends_on_capability` and `capability_implemented_by_feature`
|
|
4580
|
-
// in the edge catalogue. The self-loop guard (UPG-520) keeps A → A refused;
|
|
4581
|
-
// A → B between distinct capabilities is the supported decomposition.
|
|
4582
|
-
capability: ["capability", "feature"],
|
|
4583
|
-
// ── Market hierarchy ────────────────────────────────────────────────────────
|
|
4584
|
-
// competitive_analysis hosts classification axes (its dimensions);
|
|
4585
|
-
// each axis hosts its values; values are reusable across the row and column
|
|
4586
|
-
// dossiers of a 2-axis matrix.
|
|
4587
|
-
competitive_analysis: ["competitor", "market_trend", "market_segment", "classification_axis"],
|
|
4588
|
-
competitor: ["competitor_feature", "competitor_signal"],
|
|
4589
|
-
classification_axis: ["classification_value"],
|
|
4590
|
-
// ── UX Research hierarchy ───────────────────────────────────────────────────
|
|
4591
|
-
research_study: [
|
|
4592
|
-
"participant",
|
|
4593
|
-
"observation",
|
|
4594
|
-
"affinity_cluster",
|
|
4595
|
-
"research_question",
|
|
4596
|
-
"interview_guide",
|
|
4597
|
-
"insight",
|
|
4598
|
-
"survey_response"
|
|
4599
|
-
],
|
|
4600
|
-
observation: ["quote"],
|
|
4601
|
-
// F6 (UPG-672): affinity_cluster owns the observations it groups, in addition
|
|
4602
|
-
// to the insights it synthesises. Pairs with `affinity_cluster_groups_observation`
|
|
4603
|
-
// in the edge catalog (hierarchy). An observation can be both directly owned by
|
|
4604
|
-
// its research_study and grouped under an affinity_cluster (multi-parent grammar).
|
|
4605
|
-
affinity_cluster: ["insight", "observation"],
|
|
4606
|
-
// insight refines into insight; a raw insight can be distilled
|
|
4607
|
-
// into a more specific insight (refines_into chain).
|
|
4608
|
-
// v0.5.8 (UPG-528 Part 2d): insights own their evidencing quotes;
|
|
4609
|
-
// parallel to `observation: ['quote']`. Research-synthesis canon.
|
|
4610
|
-
// Pairs with `insight_evidenced_by_quote` in the edge catalog.
|
|
4611
|
-
// ── Design hierarchy ────────────────────────────────────────────────────────
|
|
4612
|
-
// (UPG-663, v0.9.2) A user_journey owns its steps (the stable 0.1.0 spine)
|
|
4613
|
-
// and carries phases as a non-owning band overlay. A journey_phase is NO
|
|
4614
|
-
// LONGER a containment parent of journey_step: it SPANS steps via the
|
|
4615
|
-
// `journey_phase_spans_journey_step` edge (mirroring the marketing
|
|
4616
|
-
// `customer_journey_stage_spans_journey_step` precedent), so each step has
|
|
4617
|
-
// exactly one containment parent and the journey has one canonical step list.
|
|
4618
|
-
user_journey: ["journey_step", "journey_phase"],
|
|
4619
|
-
journey_step: ["journey_action"],
|
|
4620
|
-
// v0.5.8 (UPG-528 Part 2d): insights own their evidencing quotes;
|
|
4621
|
-
// parallel to `observation: ['quote']`. Research-synthesis canon
|
|
4622
|
-
// (insight ← quote evidencing) extends quote's hierarchy parents to
|
|
4623
|
-
// both observation (raw-capture parent) and insight (synthesis parent).
|
|
4624
|
-
// Pairs with `insight_evidenced_by_quote` in the edge catalog.
|
|
4625
|
-
insight: ["design_question", "insight", "quote"],
|
|
4626
|
-
// v0.5.2 (UPG-528): need anchors a Wardley value chain; capabilities
|
|
4627
|
-
// fulfil the need at the chain top. Cross-domain hierarchy (need ∈ Design,
|
|
4628
|
-
// capability ∈ Strategy) is intentional: Wardley analysis starts from a
|
|
4629
|
-
// user need and decomposes into the capabilities required to fulfil it.
|
|
4630
|
-
need: ["design_question", "capability"],
|
|
4631
|
-
design_question: ["design_concept"],
|
|
4632
|
-
design_concept: ["prototype", "wireframe"],
|
|
4633
|
-
design_component: [
|
|
4634
|
-
"design_token",
|
|
4635
|
-
"design_pattern",
|
|
4636
|
-
"design_guideline",
|
|
4637
|
-
"interaction_spec",
|
|
4638
|
-
"design_component"
|
|
4639
|
-
],
|
|
4640
|
-
// self-nesting for atomic hierarchy (organism → molecule → atom)
|
|
4641
|
-
prototype: ["annotation"],
|
|
4642
|
-
// ── Brand hierarchy ─────────────────────────────────────────────────────────
|
|
4643
|
-
brand_identity: [
|
|
4644
|
-
"brand_colour",
|
|
4645
|
-
"brand_typography",
|
|
4646
|
-
"brand_asset",
|
|
4647
|
-
"brand_voice",
|
|
4648
|
-
"brand_logo",
|
|
4649
|
-
"brand_imagery"
|
|
4650
|
-
],
|
|
4651
|
-
// ── Product Specification hierarchy ─────────────────────────────────────────
|
|
4652
|
-
// story_task collapsed into canonical task. feature now owns
|
|
4653
|
-
// task directly; story-derived tasks implement user_story via
|
|
4654
|
-
// task_implements_user_story edge.
|
|
4655
|
-
feature: ["epic", "bug", "task"],
|
|
4656
|
-
feature_area: ["feature", "feature_area"],
|
|
4657
|
-
// epic owns user_story (the templated promise) and, mirroring feature, may
|
|
4658
|
-
// also directly contain bug/task — heterogeneous imported tickets that belong
|
|
4659
|
-
// to one epic rather than the feature as a whole (feedback df99026a).
|
|
4660
|
-
epic: ["user_story", "bug", "task"],
|
|
4661
|
-
user_story: ["acceptance_criterion"],
|
|
4662
|
-
task: ["task"],
|
|
4663
|
-
// planning_cycle (0.20.0): the cadence axis self-nests (a program-increment
|
|
4664
|
-
// contains iterations; a cycle contains its cooldown) via
|
|
4665
|
-
// planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
|
|
4666
|
-
// child: scheduling work into a cycle is the semantic, deliberate-only
|
|
4667
|
-
// planning_cycle_schedules_work_item edge, so the item keeps its feature/epic
|
|
4668
|
-
// containment parent and is merely referenced by the cycle.
|
|
4669
|
-
planning_cycle: ["planning_cycle"],
|
|
4670
|
-
// ── Engineering hierarchy ───────────────────────────────────────────────────
|
|
4671
|
-
bounded_context: [
|
|
4672
|
-
"service",
|
|
4673
|
-
"domain_event",
|
|
4674
|
-
"decision",
|
|
4675
|
-
"data_model",
|
|
4676
|
-
"aggregate",
|
|
4677
|
-
"read_model",
|
|
4678
|
-
"code_repository",
|
|
4679
|
-
"integration_pattern",
|
|
4680
|
-
"external_api",
|
|
4681
|
-
"data_flow",
|
|
4682
|
-
// v0.5.7 (UPG-528 Part 2c): BCs publish api_contracts as their "published
|
|
4683
|
-
// language" (DDD canon). api_contract has two valid hierarchy parents now:
|
|
4684
|
-
// service (per-service exposure) AND bounded_context (BC-level surface).
|
|
4685
|
-
"api_contract"
|
|
4686
|
-
],
|
|
4687
|
-
service: [
|
|
4688
|
-
"api_contract",
|
|
4689
|
-
"technical_debt_item",
|
|
4690
|
-
"feature_flag",
|
|
4691
|
-
"deployment",
|
|
4692
|
-
"api_endpoint",
|
|
4693
|
-
"database_schema",
|
|
4694
|
-
"queue_topic",
|
|
4695
|
-
"build_artifact",
|
|
4696
|
-
"library_dependency"
|
|
4697
|
-
],
|
|
4698
|
-
// v0.5.1 (UPG-517 C2): contracts contain their endpoints. Service still
|
|
4699
|
-
// serves endpoints directly (legacy / non-contract endpoints); both
|
|
4700
|
-
// parent-child paths coexist.
|
|
4701
|
-
api_contract: ["api_endpoint"],
|
|
4702
|
-
decision: ["technical_debt_item"],
|
|
4703
|
-
aggregate: ["domain_entity", "value_object", "command"],
|
|
4704
|
-
// ── Growth hierarchy ────────────────────────────────────────────────────────
|
|
4705
|
-
funnel: ["funnel_step"],
|
|
4706
|
-
acquisition_channel: ["growth_campaign"],
|
|
4707
|
-
growth_campaign: ["experiment_plan", "variant"],
|
|
4708
|
-
// ── Business Model hierarchy ─────────────────────────────────────────────────
|
|
4709
|
-
business_model: [
|
|
4710
|
-
"value_proposition",
|
|
4711
|
-
"revenue_stream",
|
|
4712
|
-
"cost_structure",
|
|
4713
|
-
"unit_economics",
|
|
4714
|
-
"partnership",
|
|
4715
|
-
"key_resource",
|
|
4716
|
-
"key_activity",
|
|
4717
|
-
"customer_relationship",
|
|
4718
|
-
"distribution_channel"
|
|
4719
|
-
],
|
|
4720
|
-
revenue_stream: ["pricing_tier"],
|
|
4721
|
-
// ── Go-To-Market hierarchy ───────────────────────────────────────────────────
|
|
4722
|
-
gtm_strategy: [
|
|
4723
|
-
"ideal_customer_profile",
|
|
4724
|
-
"positioning",
|
|
4725
|
-
"launch",
|
|
4726
|
-
"content_strategy",
|
|
4727
|
-
"sales_motion",
|
|
4728
|
-
"competitive_battle_card",
|
|
4729
|
-
"demand_gen_program",
|
|
4730
|
-
"territory"
|
|
4731
|
-
],
|
|
4732
|
-
positioning: ["messaging", "objection", "proof_point"],
|
|
4733
|
-
value_proposition: ["objection", "proof_point"],
|
|
4734
|
-
competitive_battle_card: ["objection"],
|
|
4735
|
-
objection: ["rebuttal"],
|
|
4736
|
-
rebuttal: ["proof_point"],
|
|
4737
|
-
// ── Team & Organisation hierarchy ───────────────────────────────────────────
|
|
4738
|
-
department: ["team", "stakeholder"],
|
|
4739
|
-
team: [
|
|
4740
|
-
"role",
|
|
4741
|
-
"team_okr",
|
|
4742
|
-
"retrospective",
|
|
4743
|
-
"dependency",
|
|
4744
|
-
"skill",
|
|
4745
|
-
"ceremony",
|
|
4746
|
-
"capacity_plan",
|
|
4747
|
-
"decision",
|
|
4748
|
-
// Self-nesting (0.17.2): a parent team contains its sub-teams / squads, one
|
|
4749
|
-
// level below department_contains_team. Backs team_contains_team. Mirrors
|
|
4750
|
-
// feature_area_contains_feature_area (same-type child listed last).
|
|
4751
|
-
"team"
|
|
4752
|
-
],
|
|
4753
|
-
// ── Data & Analytics hierarchy ───────────────────────────────────────────────
|
|
4754
|
-
data_source: ["metric", "data_pipeline", "data_lineage", "event_schema"],
|
|
4755
|
-
// UPG-685 T0.4 (0.13.0 Wave 1): `outcome` removed as a metric child. It closed a
|
|
4756
|
-
// containment cycle (outcome ⊃ metric ⊃ outcome) and was backed only by the CAUSAL
|
|
4757
|
-
// `metric_drives_outcome` edge, not a hierarchy edge — the "metric leads to outcome"
|
|
4758
|
-
// meaning lives correctly on that causal edge. The real containment (outcome ⊃ metric)
|
|
4759
|
-
// is kept. Sub-metric trees (metric ⊃ metric) stay.
|
|
4760
|
-
metric: ["metric", "data_quality_rule", "metric_quality_assessment"],
|
|
4761
|
-
data_domain: ["data_product", "data_source", "glossary_term", "data_model", "dashboard"],
|
|
4762
|
-
dashboard: ["report", "experiment_run"],
|
|
4763
|
-
// ── Content & Knowledge hierarchy ────────────────────────────────────────────
|
|
4764
|
-
// release contains features and bugs (GitHub milestone→issue import).
|
|
4765
|
-
release: ["changelog", "feature", "bug"],
|
|
4766
|
-
content_strategy: ["content_calendar", "content_theme"],
|
|
4767
|
-
content_calendar: [
|
|
4768
|
-
"content_theme",
|
|
4769
|
-
"content_piece",
|
|
4770
|
-
"knowledge_base_article",
|
|
4771
|
-
"brand_asset",
|
|
4772
|
-
"document",
|
|
4773
|
-
"documentation_template"
|
|
4774
|
-
],
|
|
4775
|
-
// ── Operations & CS hierarchy ────────────────────────────────────────────────
|
|
4776
|
-
service_blueprint: [
|
|
4777
|
-
"user_flow",
|
|
4778
|
-
"playbook",
|
|
4779
|
-
"service_level_agreement",
|
|
4780
|
-
"customer_health_score",
|
|
4781
|
-
"support_ticket",
|
|
4782
|
-
"customer_feedback"
|
|
4783
|
-
],
|
|
4784
|
-
customer_health_score: ["nps_campaign", "success_milestone"],
|
|
4785
|
-
customer_feedback: ["churn_reason"],
|
|
4786
|
-
user_flow: ["screen", "customer_journey_stage"],
|
|
4787
|
-
customer_journey_stage: ["touchpoint"],
|
|
4788
|
-
// ── Legal, Compliance & Risk hierarchy ──────────────────────────────────────
|
|
4789
|
-
legal_entity: ["ip_asset", "contract"],
|
|
4790
|
-
contract: ["contract_clause"],
|
|
4791
|
-
compliance_framework: [
|
|
4792
|
-
"security_audit",
|
|
4793
|
-
"compliance_requirement",
|
|
4794
|
-
"privacy_policy",
|
|
4795
|
-
"audit_log_policy",
|
|
4796
|
-
"risk",
|
|
4797
|
-
"data_contract",
|
|
4798
|
-
"legal_entity"
|
|
4799
|
-
],
|
|
4800
|
-
// ── DevOps & Platform hierarchy ──────────────────────────────────────────────
|
|
4801
|
-
infrastructure_component: [
|
|
4802
|
-
"service_level_objective",
|
|
4803
|
-
"monitor",
|
|
4804
|
-
"ci_pipeline",
|
|
4805
|
-
"incident",
|
|
4806
|
-
"runbook",
|
|
4807
|
-
"release_strategy",
|
|
4808
|
-
"on_call_rotation"
|
|
4809
|
-
],
|
|
4810
|
-
service_level_objective: ["service_level_indicator", "error_budget"],
|
|
4811
|
-
incident: ["postmortem"],
|
|
4812
|
-
monitor: ["alert_rule"],
|
|
4813
|
-
ci_pipeline: ["build_artifact"],
|
|
4814
|
-
// ── Security hierarchy ───────────────────────────────────────────────────────
|
|
4815
|
-
security_policy: [
|
|
4816
|
-
"security_control",
|
|
4817
|
-
"access_policy",
|
|
4818
|
-
"data_classification",
|
|
4819
|
-
"threat_model",
|
|
4820
|
-
"security_review",
|
|
4821
|
-
"incident"
|
|
4822
|
-
],
|
|
4823
|
-
security_review: ["penetration_test"],
|
|
4824
|
-
threat_model: ["threat", "vulnerability"],
|
|
4825
|
-
// ── Sales & Revenue hierarchy ────────────────────────────────────────────────
|
|
4826
|
-
pipeline_sales: ["pipeline_stage", "lead", "account", "forecast", "subscription"],
|
|
4827
|
-
account: ["contact", "deal"],
|
|
4828
|
-
deal: ["quote_document"],
|
|
4829
|
-
subscription: ["invoice"],
|
|
4830
|
-
// ── Program Management hierarchy ─────────────────────────────────────────────
|
|
4831
|
-
program: ["project", "risk_register", "change_request", "resource_allocation", "status_report"],
|
|
4832
|
-
project: ["milestone", "deliverable"],
|
|
4833
|
-
risk_register: ["risk"],
|
|
4834
|
-
// ── Accessibility hierarchy ──────────────────────────────────────────────────
|
|
4835
|
-
a11y_standard: ["a11y_guideline", "a11y_audit", "a11y_annotation"],
|
|
4836
|
-
a11y_audit: ["a11y_issue"],
|
|
4837
|
-
// ── Product Specification expansion ─────────────────────────────────────────
|
|
4838
|
-
roadmap: ["roadmap_item", "roadmap_theme", "release"],
|
|
4839
|
-
roadmap_theme: ["feature"],
|
|
4840
|
-
// ── Unified Context Layer hierarchy ─────────────────────────────────────────
|
|
4841
|
-
design_system: [
|
|
4842
|
-
"design_component",
|
|
4843
|
-
"design_token",
|
|
4844
|
-
"design_guideline",
|
|
4845
|
-
"brand_identity",
|
|
4846
|
-
"user_journey",
|
|
4847
|
-
"user_flow",
|
|
4848
|
-
"insight"
|
|
4849
|
-
],
|
|
4850
|
-
screen: ["screen_state", "screen", "surface", "design_component", "wireframe"],
|
|
4851
|
-
// self-nesting + surfaces + components + wireframes
|
|
4852
|
-
// surface (0.27.0): the place inside a screen. Self-nests, so a shell holds
|
|
4853
|
-
// panes, a pane holds regions, a region holds slots. A surface is reached
|
|
4854
|
-
// through the screen that renders it (`screen: [... 'surface']`), never as a
|
|
4855
|
-
// top-level product child: every UPG_VALID_CHILDREN pair must be backed by a
|
|
4856
|
-
// hierarchy-classified edge (guardrail G2b), and the ratified ten do not
|
|
4857
|
-
// include a product-to-surface verb. An app-shell surface therefore hangs off
|
|
4858
|
-
// the screen that mounts it.
|
|
4859
|
-
//
|
|
4860
|
-
// NOTE: `surface_kind` narrows this further than UPG_VALID_CHILDREN can
|
|
4861
|
-
// express. UPG_VALID_CHILDREN is keyed on entity TYPE, so it can only say
|
|
4862
|
-
// "a surface may contain a surface"; the kind ordering (shell > tool > pane >
|
|
4863
|
-
// region > slot / gutter / action_bar, with overlay and ambient parented by
|
|
4864
|
-
// whatever raises them) is a property-level rule documented on `SurfaceKind`
|
|
4865
|
-
// and enforced by validators, not by this table. Precedent: the same split
|
|
4866
|
-
// applies to `planning_cycle`, whose `cadence_kind` narrows its self-nesting.
|
|
4867
|
-
surface: ["surface", "design_component"],
|
|
4868
|
-
// ── Marketing & Communications hierarchy ────────────────────────────────────
|
|
4869
|
-
marketing_strategy: [
|
|
4870
|
-
"marketing_channel",
|
|
4871
|
-
"seo_keyword",
|
|
4872
|
-
"press_release",
|
|
4873
|
-
"event",
|
|
4874
|
-
"community_initiative"
|
|
4875
|
-
],
|
|
4876
|
-
marketing_channel: ["marketing_campaign_plan"],
|
|
4877
|
-
marketing_campaign_plan: ["email_sequence", "social_post", "ad_creative"],
|
|
4878
|
-
// ── Localisation & i18n hierarchy ───────────────────────────────────────────
|
|
4879
|
-
locale: ["translation_bundle", "cultural_adaptation", "regional_pricing", "locale_config"],
|
|
4880
|
-
translation_bundle: ["translation_key"],
|
|
4881
|
-
// ── Customer Education & Training hierarchy ──────────────────────────────────
|
|
4882
|
-
education_program: [
|
|
4883
|
-
"tutorial",
|
|
4884
|
-
"walkthrough",
|
|
4885
|
-
"webinar",
|
|
4886
|
-
"certification",
|
|
4887
|
-
"help_video",
|
|
4888
|
-
"learning_path"
|
|
4889
|
-
],
|
|
4890
|
-
// a learning_path can include its terminal certification.
|
|
4891
|
-
learning_path: ["tutorial", "certification"],
|
|
4892
|
-
// ── Quality Assurance & Testing hierarchy ────────────────────────────────────
|
|
4893
|
-
// (UPG-678) test_plan re-homed validation → QA. It is the QA planning layer:
|
|
4894
|
-
// the verification approach (scope, environments, pass criteria) that the
|
|
4895
|
-
// test suites execute. A test_plan groups the suites that carry it out.
|
|
4896
|
-
test_plan: ["test_suite", "test_environment"],
|
|
4897
|
-
test_suite: [
|
|
4898
|
-
"test_case",
|
|
4899
|
-
"regression_test",
|
|
4900
|
-
"qa_session",
|
|
4901
|
-
"test_coverage_report",
|
|
4902
|
-
"test_environment",
|
|
4903
|
-
"test_result"
|
|
4904
|
-
],
|
|
4905
|
-
test_case: ["test_result"],
|
|
4906
|
-
qa_session: ["bug"],
|
|
4907
|
-
// ── Partner & Ecosystem Management hierarchy ─────────────────────────────────
|
|
4908
|
-
partner_program: [
|
|
4909
|
-
"partner_tier",
|
|
4910
|
-
"integration_partner",
|
|
4911
|
-
"partner_revenue_share",
|
|
4912
|
-
"api_ecosystem",
|
|
4913
|
-
"developer_portal"
|
|
4914
|
-
],
|
|
4915
|
-
api_ecosystem: ["marketplace_listing"],
|
|
4916
|
-
// ── Feedback & Voice of Customer hierarchy ───────────────────────────────────
|
|
4917
|
-
feedback_program: [
|
|
4918
|
-
"feature_request",
|
|
4919
|
-
"nps_campaign",
|
|
4920
|
-
"feedback_theme",
|
|
4921
|
-
"user_advisory_board",
|
|
4922
|
-
"beta_program"
|
|
4923
|
-
],
|
|
4924
|
-
feature_request: ["feedback_vote"],
|
|
4925
|
-
// v0.5.8 (UPG-528 Part 2d): CABs convene as quarterly ceremonies;
|
|
4926
|
-
// CAB owns its meeting cadence (Stettler, Moore CAB playbook canon).
|
|
4927
|
-
// Pairs with `user_advisory_board_convenes_as_ceremony` in the edge
|
|
4928
|
-
// catalog. ceremony now has two valid hierarchy parents (team for sprint
|
|
4929
|
-
// ceremonies, user_advisory_board for CAB meetings).
|
|
4930
|
-
user_advisory_board: ["ceremony"],
|
|
4931
|
-
// ── Pricing & Packaging hierarchy ────────────────────────────────────────────
|
|
4932
|
-
pricing_strategy: [
|
|
4933
|
-
"experiment_plan",
|
|
4934
|
-
"pricing_tier",
|
|
4935
|
-
"discount_strategy",
|
|
4936
|
-
"trial_config",
|
|
4937
|
-
"paywall"
|
|
4938
|
-
],
|
|
4939
|
-
// ── AI/ML Operations hierarchy ───────────────────────────────────────────────
|
|
4940
|
-
// (UPG-665) The prompt abstraction is corrected: ai_model → prompt_template →
|
|
4941
|
-
// prompt_version (a model has templates; each template has versions). The
|
|
4942
|
-
// model no longer owns prompt_version directly.
|
|
4943
|
-
ai_model: [
|
|
4944
|
-
"prompt_template",
|
|
4945
|
-
"eval_benchmark",
|
|
4946
|
-
"ai_cost_tracker",
|
|
4947
|
-
"hallucination_report",
|
|
4948
|
-
"ai_guardrail",
|
|
4949
|
-
"model_comparison",
|
|
4950
|
-
"ai_experiment",
|
|
4951
|
-
"ai_dataset",
|
|
4952
|
-
"ai_trace"
|
|
4953
|
-
],
|
|
4954
|
-
// prompt_template owns its prompt_versions the way a file owns its commits.
|
|
4955
|
-
prompt_template: ["prompt_version"],
|
|
4956
|
-
// v0.5.7 (UPG-528 Part 2c): benchmarks define the metric set they measure
|
|
4957
|
-
// (HELM, MLPerf, BIG-bench all spec a metric list). metric already has many
|
|
4958
|
-
// hierarchy parents (outcome, objective, key_result, solution, data_source).
|
|
4959
|
-
eval_benchmark: ["eval_run", "metric"],
|
|
4960
|
-
// ai_trace spawns child traces for sub-calls (tool calls, chained
|
|
4961
|
-
// prompts). Self-nesting enables call-tree representation.
|
|
4962
|
-
ai_trace: ["ai_trace"],
|
|
4963
|
-
// ── Agentic Workflows & Process hierarchy ────────────────────────────────────
|
|
4964
|
-
workflow_template: ["workflow_run", "review_gate", "agent_task"],
|
|
4965
|
-
workflow_run: ["workflow_artifact"],
|
|
4966
|
-
agent_definition: ["agent_session", "agent_skill", "agent_hook", "workflow_template", "agent_task"],
|
|
4967
|
-
// v0.5.8 (UPG-528 Part 2d): review gates also vet research insights
|
|
4968
|
-
// (ResearchOps insight-review pattern). Pairs with `review_gate_vets_insight`
|
|
4969
|
-
// in the edge catalog. The gate now owns two child types: approval records
|
|
4970
|
-
// for workflow gates, insights for research-democratisation gates.
|
|
4971
|
-
review_gate: ["approval_record", "insight"],
|
|
4972
|
-
// ── Investigation hierarchy ──────────────────────────────────────────────────
|
|
4973
|
-
investigation: ["symptom", "root_cause"],
|
|
4974
|
-
root_cause: ["fix"],
|
|
4975
|
-
// ── Portfolio layer hierarchy ────────────────────────────────────────────────
|
|
4976
|
-
// organization → product_area is the org-axis anchor (who owns what);
|
|
4977
|
-
// organization → portfolio is the strategic-axis anchor (where you invest).
|
|
4978
|
-
// organization → workspace (WS3, 2026-07-05): org-altitude workspace anchor
|
|
4979
|
-
// (organization_thinks_in_workspace) — a workspace scoped to org altitude
|
|
4980
|
-
// lives in the same portfolio-level graph as its organization node.
|
|
4981
|
-
organization: ["portfolio", "product_area", "workspace"],
|
|
4982
|
-
// portfolios can nest (multi-level investment structures).
|
|
4983
|
-
portfolio: ["product", "portfolio"],
|
|
4984
|
-
// product_area is the organisational container for products and can
|
|
4985
|
-
// nest (parent → sub-area). product_area also groups features; the
|
|
4986
|
-
// "Studio area owns 6 features" mental model. product_area → workspace
|
|
4987
|
-
// (WS3, 2026-07-05): plane-altitude workspace anchor
|
|
4988
|
-
// (product_area_thinks_in_workspace) — "plane" is Entopo's app-level name
|
|
4989
|
-
// for product_area, not a distinct spec entity.
|
|
4990
|
-
product_area: ["product", "product_area", "feature", "workspace"]
|
|
4991
|
-
};
|
|
4992
|
-
function getValidChildren(entityType) {
|
|
4993
|
-
return UPG_VALID_CHILDREN[entityType] ?? [];
|
|
4994
|
-
}
|
|
4995
|
-
function canBeChildOf(childType, parentType) {
|
|
4996
|
-
return getValidChildren(parentType).includes(childType);
|
|
4997
|
-
}
|
|
4998
|
-
var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
4999
|
-
"person",
|
|
5000
|
-
// A framework_exercise has no structural parent: it anchors to the entities
|
|
5001
|
-
// it scores via the `framework_exercise_includes_node` edge, not by
|
|
5002
|
-
// containment. Modelling it as a product child would force a containment edge
|
|
5003
|
-
// and read against the whole point of the exercise model (relational, not
|
|
5004
|
-
// hierarchical). Same posture as `person`. See ADR 2026-06-02-framework-exercises.
|
|
5005
|
-
"framework_exercise",
|
|
5006
|
-
// A composition has no structural parent for the same reason a
|
|
5007
|
-
// framework_exercise does not: it anchors to what it SHOWS, relationally, via
|
|
5008
|
-
// `composition_focuses_node`. Modelling it as a product child would force a
|
|
5009
|
-
// containment edge that reads against the point — a published view is not a
|
|
5010
|
-
// part of the product, it is a lens onto one. This is also what keeps the
|
|
5011
|
-
// type to two edges rather than three: no provenance edge is minted (that is
|
|
5012
|
-
// `workspace_produced_node`, reused) and no parent edge is needed.
|
|
5013
|
-
"composition",
|
|
5014
|
-
// A capture has no structural parent for the same reason: it anchors to what
|
|
5015
|
-
// it RENDERS, via `capture_renders_node`. Giving it a product parent would
|
|
5016
|
-
// claim a capture is part of the product rather than a picture of one, and
|
|
5017
|
-
// would mint a containment edge whose only job is to satisfy the hierarchy.
|
|
5018
|
-
"capture"
|
|
5019
|
-
]);
|
|
5020
|
-
function isContainmentFreeType(entityType) {
|
|
5021
|
-
return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
|
|
5022
|
-
}
|
|
5023
|
-
|
|
5024
|
-
// src/grammar/projection.ts
|
|
5025
|
-
var VARIES_BY_EDGE = "surface_varies_by_configuration_axis";
|
|
5026
|
-
var PRESENT_UNDER_PROPERTY = "present_under";
|
|
5027
|
-
var ACTIVE_WHEN_PROPERTY = "active_when";
|
|
5028
|
-
var QUALIFIABLE_EDGE_TYPES = [
|
|
5029
|
-
"surface_contains_surface",
|
|
5030
|
-
"feature_occupies_surface"
|
|
5031
|
-
];
|
|
5032
|
-
function readStringArray(properties, key) {
|
|
5033
|
-
const raw = properties?.[key];
|
|
5034
|
-
if (!Array.isArray(raw)) return void 0;
|
|
5035
|
-
return raw.filter((v) => typeof v === "string");
|
|
5036
|
-
}
|
|
5037
|
-
function readActiveWhen(edge) {
|
|
5038
|
-
const raw = edge.properties?.[ACTIVE_WHEN_PROPERTY];
|
|
5039
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
5040
|
-
const axis = raw.axis;
|
|
5041
|
-
const values = readStringArray(raw, "values");
|
|
5042
|
-
if (typeof axis !== "string" || axis.length === 0 || !values || values.length === 0) {
|
|
5043
|
-
return void 0;
|
|
5044
|
-
}
|
|
5045
|
-
return { axis, values };
|
|
5046
|
-
}
|
|
5047
|
-
function projectGraph(nodes, edges, configuration) {
|
|
5048
|
-
const axes = Object.keys(configuration);
|
|
5049
|
-
if (axes.length === 0) {
|
|
5050
|
-
return {
|
|
5051
|
-
nodes: [...nodes],
|
|
5052
|
-
edges: [...edges],
|
|
5053
|
-
excluded_node_ids: [],
|
|
5054
|
-
deactivated_edge_count: 0,
|
|
5055
|
-
dangling_edge_count: 0
|
|
5056
|
-
};
|
|
5057
|
-
}
|
|
5058
|
-
const excluded = /* @__PURE__ */ new Set();
|
|
5059
|
-
for (const edge of edges) {
|
|
5060
|
-
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
5061
|
-
const chosen = configuration[edge.target];
|
|
5062
|
-
if (chosen === void 0) continue;
|
|
5063
|
-
const presentUnder = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY);
|
|
5064
|
-
if (!presentUnder) continue;
|
|
5065
|
-
if (!presentUnder.includes(chosen)) excluded.add(edge.source);
|
|
5066
|
-
}
|
|
5067
|
-
const survivingNodes = excluded.size === 0 ? [...nodes] : nodes.filter((n) => !excluded.has(n.id));
|
|
5068
|
-
let deactivated = 0;
|
|
5069
|
-
let dangling = 0;
|
|
5070
|
-
const survivingEdges = [];
|
|
5071
|
-
for (const edge of edges) {
|
|
5072
|
-
const qualifier = readActiveWhen(edge);
|
|
5073
|
-
if (qualifier) {
|
|
5074
|
-
const chosen = configuration[qualifier.axis];
|
|
5075
|
-
if (chosen !== void 0 && !qualifier.values.includes(chosen)) {
|
|
5076
|
-
deactivated++;
|
|
5077
|
-
continue;
|
|
5078
|
-
}
|
|
5079
|
-
}
|
|
5080
|
-
if (excluded.has(edge.source) || excluded.has(edge.target)) {
|
|
5081
|
-
dangling++;
|
|
5082
|
-
continue;
|
|
5083
|
-
}
|
|
5084
|
-
survivingEdges.push(edge);
|
|
5085
|
-
}
|
|
5086
|
-
return {
|
|
5087
|
-
nodes: survivingNodes,
|
|
5088
|
-
edges: survivingEdges,
|
|
5089
|
-
excluded_node_ids: [...excluded].sort(),
|
|
5090
|
-
deactivated_edge_count: deactivated,
|
|
5091
|
-
dangling_edge_count: dangling
|
|
5092
|
-
};
|
|
5093
|
-
}
|
|
5094
|
-
function enumerateProjections(nodes) {
|
|
5095
|
-
const out = [
|
|
5096
|
-
{ configuration: {} }
|
|
5097
|
-
];
|
|
5098
|
-
for (const node of nodes) {
|
|
5099
|
-
if (node.type !== "configuration_axis") continue;
|
|
5100
|
-
const values = readStringArray(node.properties, "values");
|
|
5101
|
-
if (!values) continue;
|
|
5102
|
-
for (const value of values) {
|
|
5103
|
-
out.push({ axis: node.id, value, configuration: { [node.id]: value } });
|
|
5104
|
-
}
|
|
5105
|
-
}
|
|
5106
|
-
return out;
|
|
5107
|
-
}
|
|
5108
|
-
|
|
5109
|
-
// src/grammar/configuration-drift.ts
|
|
5110
|
-
var ALTERNATES_EDGE = "surface_alternates_with_surface";
|
|
5111
|
-
var PARENT_EDGE_TYPES = /* @__PURE__ */ new Set(["surface_contains_surface", "screen_renders_surface"]);
|
|
5112
|
-
function edgeLabel(edge) {
|
|
5113
|
-
return edge.id ?? `${edge.source} -> ${edge.target}`;
|
|
5114
|
-
}
|
|
5115
|
-
function checkConfigurationDrift(nodes, edges) {
|
|
5116
|
-
const findings = [];
|
|
5117
|
-
const axisValues = /* @__PURE__ */ new Map();
|
|
5118
|
-
for (const node of nodes) {
|
|
5119
|
-
if (node.type !== "configuration_axis") continue;
|
|
5120
|
-
const values = readStringArray(node.properties, "values");
|
|
5121
|
-
if (!values || values.length === 0) {
|
|
5122
|
-
findings.push({
|
|
5123
|
-
kind: "axis_values_empty",
|
|
5124
|
-
severity: "error",
|
|
5125
|
-
node_id: node.id,
|
|
5126
|
-
axis_id: node.id,
|
|
5127
|
-
message: "Configuration axis declares no values. An axis with no values selects nothing, so no projection can be taken along it."
|
|
5128
|
-
});
|
|
5129
|
-
axisValues.set(node.id, []);
|
|
5130
|
-
continue;
|
|
5131
|
-
}
|
|
5132
|
-
axisValues.set(node.id, values);
|
|
5133
|
-
const rawDefault = node.properties?.default_value;
|
|
5134
|
-
if (typeof rawDefault === "string" && rawDefault.length > 0 && !values.includes(rawDefault)) {
|
|
5135
|
-
findings.push({
|
|
5136
|
-
kind: "axis_default_not_a_value",
|
|
5137
|
-
severity: "error",
|
|
5138
|
-
node_id: node.id,
|
|
5139
|
-
axis_id: node.id,
|
|
5140
|
-
message: `default_value "${rawDefault}" is not one of the axis values [${values.join(", ")}]. It names a value of this axis, so it has to be one the axis declares.`
|
|
5141
|
-
});
|
|
5142
|
-
}
|
|
5143
|
-
}
|
|
5144
|
-
for (const edge of edges) {
|
|
5145
|
-
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
5146
|
-
const values = axisValues.get(edge.target);
|
|
5147
|
-
const presentUnder = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY);
|
|
5148
|
-
if (!presentUnder || presentUnder.length === 0) {
|
|
5149
|
-
findings.push({
|
|
5150
|
-
kind: "present_under_empty",
|
|
5151
|
-
severity: "error",
|
|
5152
|
-
edge_id: edge.id,
|
|
5153
|
-
node_id: edge.source,
|
|
5154
|
-
axis_id: edge.target,
|
|
5155
|
-
message: `${edgeLabel(edge)} declares variance without a non-empty present_under. A surface that exists under no configuration should be deleted rather than declared; a surface that exists under all of them should carry no varies_by edge at all.`
|
|
5156
|
-
});
|
|
5157
|
-
continue;
|
|
5158
|
-
}
|
|
5159
|
-
if (!values) continue;
|
|
5160
|
-
for (const value of presentUnder) {
|
|
5161
|
-
if (!values.includes(value)) {
|
|
5162
|
-
findings.push({
|
|
5163
|
-
kind: "present_under_unknown_value",
|
|
5164
|
-
severity: "error",
|
|
5165
|
-
edge_id: edge.id,
|
|
5166
|
-
node_id: edge.source,
|
|
5167
|
-
axis_id: edge.target,
|
|
5168
|
-
message: `present_under names "${value}", which the axis does not declare. Its values are [${values.join(", ")}].`
|
|
5169
|
-
});
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
}
|
|
5173
|
-
for (const edge of edges) {
|
|
5174
|
-
const hasQualifierKey = edge.properties !== void 0 && Object.prototype.hasOwnProperty.call(edge.properties, ACTIVE_WHEN_PROPERTY);
|
|
5175
|
-
if (!hasQualifierKey) continue;
|
|
5176
|
-
if (edge.type !== void 0 && !QUALIFIABLE_EDGE_TYPES.includes(edge.type)) {
|
|
5177
|
-
findings.push({
|
|
5178
|
-
kind: "qualifier_on_illegal_edge",
|
|
5179
|
-
severity: "error",
|
|
5180
|
-
edge_id: edge.id,
|
|
5181
|
-
message: `active_when is not legal on ${edge.type}. The qualifier is scoped to [${QUALIFIABLE_EDGE_TYPES.join(", ")}] deliberately: conditional composition is the question this release answers, and a general modality system is not.`
|
|
5182
|
-
});
|
|
5183
|
-
continue;
|
|
5184
|
-
}
|
|
5185
|
-
const qualifier = readActiveWhen(edge);
|
|
5186
|
-
if (!qualifier) {
|
|
5187
|
-
const rawQualifier = edge.properties?.[ACTIVE_WHEN_PROPERTY];
|
|
5188
|
-
const rawValues = rawQualifier && typeof rawQualifier === "object" && !Array.isArray(rawQualifier) ? rawQualifier.values : void 0;
|
|
5189
|
-
if (Array.isArray(rawValues) && rawValues.length === 0) {
|
|
5190
|
-
findings.push({
|
|
5191
|
-
kind: "qualifier_values_empty",
|
|
5192
|
-
severity: "error",
|
|
5193
|
-
edge_id: edge.id,
|
|
5194
|
-
message: `${edgeLabel(edge)} carries an active_when with an empty values list. Read literally that would remove the relationship from every configuration, which is a way to delete an edge by writing a property; it is treated as absent instead. To say a relationship never holds, delete the edge.`
|
|
5195
|
-
});
|
|
5196
|
-
continue;
|
|
5197
|
-
}
|
|
5198
|
-
findings.push({
|
|
5199
|
-
kind: "qualifier_axis_unresolved",
|
|
5200
|
-
severity: "error",
|
|
5201
|
-
edge_id: edge.id,
|
|
5202
|
-
message: `${edgeLabel(edge)} carries a malformed active_when. It must be an object with an axis id and a non-empty values array; anything else reads as absent, which silently makes the relationship invariant.`
|
|
5203
|
-
});
|
|
5204
|
-
continue;
|
|
5205
|
-
}
|
|
5206
|
-
const values = axisValues.get(qualifier.axis);
|
|
5207
|
-
if (!values) {
|
|
5208
|
-
findings.push({
|
|
5209
|
-
kind: "qualifier_axis_unresolved",
|
|
5210
|
-
severity: "error",
|
|
5211
|
-
edge_id: edge.id,
|
|
5212
|
-
axis_id: qualifier.axis,
|
|
5213
|
-
message: `active_when names axis "${qualifier.axis}", which is not a configuration_axis node in this graph.`
|
|
5214
|
-
});
|
|
5215
|
-
continue;
|
|
5216
|
-
}
|
|
5217
|
-
for (const value of qualifier.values) {
|
|
5218
|
-
if (!values.includes(value)) {
|
|
5219
|
-
findings.push({
|
|
5220
|
-
kind: "qualifier_unknown_value",
|
|
5221
|
-
severity: "error",
|
|
5222
|
-
edge_id: edge.id,
|
|
5223
|
-
axis_id: qualifier.axis,
|
|
5224
|
-
message: `active_when names value "${value}", which axis "${qualifier.axis}" does not declare. Its values are [${values.join(", ")}].`
|
|
5225
|
-
});
|
|
5226
|
-
}
|
|
5227
|
-
}
|
|
5228
|
-
}
|
|
5229
|
-
const varianceBySource = /* @__PURE__ */ new Map();
|
|
5230
|
-
for (const edge of edges) {
|
|
5231
|
-
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
5232
|
-
const values = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY) ?? [];
|
|
5233
|
-
const list = varianceBySource.get(edge.source) ?? [];
|
|
5234
|
-
list.push({ axis: edge.target, values });
|
|
5235
|
-
varianceBySource.set(edge.source, list);
|
|
5236
|
-
}
|
|
5237
|
-
for (const edge of edges) {
|
|
5238
|
-
if (edge.type !== ALTERNATES_EDGE) continue;
|
|
5239
|
-
const left = varianceBySource.get(edge.source) ?? [];
|
|
5240
|
-
const right = varianceBySource.get(edge.target) ?? [];
|
|
5241
|
-
if (left.length === 0 || right.length === 0) {
|
|
5242
|
-
findings.push({
|
|
5243
|
-
kind: "alternation_axis_mismatch",
|
|
5244
|
-
severity: "error",
|
|
5245
|
-
edge_id: edge.id,
|
|
5246
|
-
message: `${edgeLabel(edge)} declares an alternation, but at least one endpoint declares no variance at all. Two surfaces that are both always present are not alternatives; they co-exist.`
|
|
5247
|
-
});
|
|
5248
|
-
continue;
|
|
5249
|
-
}
|
|
5250
|
-
const sharedAxes = left.filter((l) => right.some((r) => r.axis === l.axis)).map((l) => l.axis);
|
|
5251
|
-
if (sharedAxes.length === 0) {
|
|
5252
|
-
findings.push({
|
|
5253
|
-
kind: "alternation_axis_mismatch",
|
|
5254
|
-
severity: "error",
|
|
5255
|
-
edge_id: edge.id,
|
|
5256
|
-
message: `${edgeLabel(edge)} declares an alternation between surfaces that vary on different axes. Alternatives are selected by ONE lever; surfaces on different levers can both be present at once.`
|
|
5257
|
-
});
|
|
5258
|
-
continue;
|
|
5259
|
-
}
|
|
5260
|
-
for (const axis of sharedAxes) {
|
|
5261
|
-
const leftValues = left.find((l) => l.axis === axis)?.values ?? [];
|
|
5262
|
-
const rightValues = right.find((r) => r.axis === axis)?.values ?? [];
|
|
5263
|
-
const overlap = leftValues.filter((v) => rightValues.includes(v));
|
|
5264
|
-
if (overlap.length > 0) {
|
|
5265
|
-
findings.push({
|
|
5266
|
-
kind: "alternation_overlap",
|
|
5267
|
-
severity: "error",
|
|
5268
|
-
edge_id: edge.id,
|
|
5269
|
-
axis_id: axis,
|
|
5270
|
-
message: `${edgeLabel(edge)} declares an alternation, but both surfaces are present under [${overlap.join(", ")}] on axis "${axis}". They co-exist there, so the graph is asserting "one of these" and "both, together" about the same pair.`
|
|
5271
|
-
});
|
|
5272
|
-
}
|
|
5273
|
-
}
|
|
5274
|
-
}
|
|
5275
|
-
const parentsByChild = /* @__PURE__ */ new Map();
|
|
5276
|
-
for (const edge of edges) {
|
|
5277
|
-
if (edge.type && PARENT_EDGE_TYPES.has(edge.type)) {
|
|
5278
|
-
parentsByChild.set(edge.target, (parentsByChild.get(edge.target) ?? 0) + 1);
|
|
5279
|
-
}
|
|
5280
|
-
}
|
|
5281
|
-
for (const [axisId, values] of axisValues) {
|
|
5282
|
-
for (const value of values) {
|
|
5283
|
-
const projected = projectGraph(nodes, edges, { [axisId]: value });
|
|
5284
|
-
const survivingParents = /* @__PURE__ */ new Map();
|
|
5285
|
-
for (const edge of projected.edges) {
|
|
5286
|
-
if (edge.type && PARENT_EDGE_TYPES.has(edge.type)) {
|
|
5287
|
-
survivingParents.set(edge.target, (survivingParents.get(edge.target) ?? 0) + 1);
|
|
5288
|
-
}
|
|
5289
|
-
}
|
|
5290
|
-
for (const node of projected.nodes) {
|
|
5291
|
-
if (node.type !== "surface") continue;
|
|
5292
|
-
if ((parentsByChild.get(node.id) ?? 0) === 0) continue;
|
|
5293
|
-
if ((survivingParents.get(node.id) ?? 0) > 0) continue;
|
|
5294
|
-
findings.push({
|
|
5295
|
-
kind: "orphaned_under_projection",
|
|
5296
|
-
severity: "warning",
|
|
5297
|
-
node_id: node.id,
|
|
5298
|
-
axis_id: axisId,
|
|
5299
|
-
message: `Surface is present under ${axisId} = "${value}" but every containment parent it has is absent there. Either it needs a parent in that configuration, or it should declare that it is absent from it too.`
|
|
5300
|
-
});
|
|
5301
|
-
}
|
|
5302
|
-
}
|
|
5303
|
-
}
|
|
5304
|
-
return findings;
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
// src/grammar/lifecycles.ts
|
|
5308
|
-
var PRODUCT_LIFECYCLE = {
|
|
5309
|
-
entity_type: "product",
|
|
5310
|
-
initial_phase: "concept",
|
|
5311
|
-
terminal_phases: ["sunset"],
|
|
5312
|
-
phases: [
|
|
5313
|
-
{
|
|
5314
|
-
id: "concept",
|
|
5315
|
-
status_category: "triage",
|
|
5316
|
-
label: "Concept",
|
|
5317
|
-
description: "Napkin idea. Problem shape and solution sketch, pre-validation.",
|
|
5318
|
-
transitions_to: ["validation", "build", "beta", "launch", "growth", "mature", "sunset"]
|
|
5319
|
-
},
|
|
5320
|
-
{
|
|
5321
|
-
id: "validation",
|
|
5322
|
-
status_category: "started",
|
|
5323
|
-
label: "Validation",
|
|
5324
|
-
description: "Testing demand. User conversations and experiments pressure-test assumptions before build commits.",
|
|
5325
|
-
transitions_to: ["build", "beta", "launch", "growth", "mature", "sunset"]
|
|
5326
|
-
},
|
|
5327
|
-
{
|
|
5328
|
-
id: "build",
|
|
5329
|
-
status_category: "started",
|
|
5330
|
-
label: "Build",
|
|
5331
|
-
description: "Actively developing v1. Core functionality is being authored, pre-user.",
|
|
5332
|
-
transitions_to: ["beta", "launch", "growth", "mature", "sunset"]
|
|
5333
|
-
},
|
|
5334
|
-
{
|
|
5335
|
-
id: "beta",
|
|
5336
|
-
status_category: "started",
|
|
5337
|
-
label: "Beta",
|
|
5338
|
-
description: "Early users, iterating. Feature-complete enough to learn from, still changing weekly.",
|
|
5339
|
-
transitions_to: ["launch", "growth", "mature", "maintenance", "sunset"]
|
|
5340
|
-
},
|
|
5341
|
-
{
|
|
5342
|
-
id: "launch",
|
|
5343
|
-
status_category: "started",
|
|
5344
|
-
label: "Launch",
|
|
5345
|
-
description: "Generally available. Announced to the target audience, open past beta access.",
|
|
5346
|
-
transitions_to: ["growth", "mature", "maintenance", "sunset"]
|
|
5347
|
-
},
|
|
5348
|
-
{
|
|
5349
|
-
id: "growth",
|
|
5350
|
-
status_category: "started",
|
|
5351
|
-
label: "Growth",
|
|
5352
|
-
description: "Scaling users and revenue. Product fit validated; focus is acquisition, retention, expansion.",
|
|
5353
|
-
transitions_to: ["mature", "maintenance", "sunset"]
|
|
5354
|
-
},
|
|
5355
|
-
{
|
|
5356
|
-
id: "mature",
|
|
5357
|
-
status_category: "started",
|
|
5358
|
-
label: "Mature",
|
|
5359
|
-
description: "Stable. Growth levelled; investment weights toward optimisation, efficiency, and durability.",
|
|
5360
|
-
transitions_to: ["maintenance", "sunset"]
|
|
5361
|
-
},
|
|
5362
|
-
{
|
|
5363
|
-
id: "maintenance",
|
|
5364
|
-
status_category: "started",
|
|
5365
|
-
label: "Maintenance",
|
|
5366
|
-
description: "Sustaining. Minimal investment keeps the product running for existing users. Can transition back to `mature` on a strategic revival.",
|
|
5367
|
-
transitions_to: ["mature", "sunset"]
|
|
5368
|
-
},
|
|
5369
|
-
{
|
|
5370
|
-
id: "sunset",
|
|
5371
|
-
status_category: "completed",
|
|
5372
|
-
label: "Sunset",
|
|
5373
|
-
description: "Winding down or retired. Existing users are being migrated or offboarded.",
|
|
5374
|
-
transitions_to: []
|
|
5375
|
-
}
|
|
5376
|
-
]
|
|
5377
|
-
};
|
|
5378
|
-
var NEED_LIFECYCLE = {
|
|
5379
|
-
entity_type: "need",
|
|
5380
|
-
initial_phase: "raw",
|
|
5381
|
-
terminal_phases: ["prioritized"],
|
|
5382
|
-
phases: [
|
|
5383
|
-
{
|
|
5384
|
-
id: "raw",
|
|
5385
|
-
status_category: "triage",
|
|
5386
|
-
label: "Raw",
|
|
5387
|
-
description: "Captured from an interview, observation, or support ticket. Articulation may be rough; may duplicate an existing need.",
|
|
5388
|
-
transitions_to: ["validated"]
|
|
5389
|
-
},
|
|
5390
|
-
{
|
|
5391
|
-
id: "validated",
|
|
5392
|
-
status_category: "started",
|
|
5393
|
-
label: "Validated",
|
|
5394
|
-
description: "Corroborated by multiple data points or targeted research. The team agrees it is a real, recurring need.",
|
|
5395
|
-
transitions_to: ["prioritized"]
|
|
5396
|
-
},
|
|
5397
|
-
{
|
|
5398
|
-
id: "prioritized",
|
|
5399
|
-
status_category: "completed",
|
|
5400
|
-
label: "Prioritized",
|
|
5401
|
-
description: "Ranked against other needs. Ready to inform opportunity definition and solution exploration.",
|
|
5402
|
-
transitions_to: []
|
|
5403
|
-
}
|
|
5404
|
-
]
|
|
5405
|
-
};
|
|
5406
|
-
var OPPORTUNITY_LIFECYCLE = {
|
|
5407
|
-
entity_type: "opportunity",
|
|
5408
|
-
initial_phase: "identified",
|
|
5409
|
-
terminal_phases: [],
|
|
5410
|
-
phases: [
|
|
5411
|
-
{
|
|
5412
|
-
id: "identified",
|
|
5413
|
-
status_category: "triage",
|
|
5414
|
-
label: "Identified",
|
|
5415
|
-
description: "A problem worth solving has been named. Sources include needs, insights, market trends, or competitive signals. Sizing and validation come next.",
|
|
5416
|
-
transitions_to: ["validated", "deferred"]
|
|
5417
|
-
},
|
|
5418
|
-
{
|
|
5419
|
-
id: "validated",
|
|
5420
|
-
status_category: "started",
|
|
5421
|
-
label: "Validated",
|
|
5422
|
-
description: "Research and evidence support pursuit. The problem is real, frequent, and painful enough to justify investment.",
|
|
5423
|
-
transitions_to: ["deferred"]
|
|
5424
|
-
},
|
|
5425
|
-
{
|
|
5426
|
-
id: "deferred",
|
|
5427
|
-
status_category: "backlog",
|
|
5428
|
-
label: "Deferred",
|
|
5429
|
-
description: "Acknowledged but parked. Capacity, priority, or strategic timing reasons. Can be reopened.",
|
|
5430
|
-
transitions_to: ["identified"]
|
|
5431
|
-
}
|
|
5432
|
-
]
|
|
4493
|
+
]
|
|
5433
4494
|
};
|
|
5434
4495
|
var SOLUTION_LIFECYCLE = {
|
|
5435
4496
|
entity_type: "solution",
|
|
@@ -7303,533 +6364,2077 @@ var TEMPLATE_LIFECYCLES = [
|
|
|
7303
6364
|
// directly above, and `monitor` and `alert_rule` are the same kind of thing:
|
|
7304
6365
|
// something that runs continuously, can be paused, and is eventually retired.
|
|
7305
6366
|
//
|
|
7306
|
-
// NOT lifecycle-free. The `user_story` precedent for lifecycle-freedom is the
|
|
7307
|
-
// Statement/Implementation split, where the paired `task` carries the
|
|
7308
|
-
// lifecycle. There is no paired entity here, so lifecycle-freedom would leave
|
|
7309
|
-
// `agent_skill` unable to say it is disabled and would entrench
|
|
7310
|
-
// `AgentHookProperties.hook_status` (now @deprecated) as the permanent status
|
|
7311
|
-
// axis, which is the *_status shadow Pattern D collapsed fourteen times.
|
|
7312
|
-
fromTemplate("agent_skill", OPERATIONAL_TEMPLATE),
|
|
7313
|
-
fromTemplate("agent_hook", OPERATIONAL_TEMPLATE),
|
|
7314
|
-
fromTemplate("on_call_rotation", OPERATIONAL_TEMPLATE),
|
|
7315
|
-
fromTemplate("security_audit", OPERATIONAL_TEMPLATE),
|
|
7316
|
-
fromTemplate("penetration_test", OPERATIONAL_TEMPLATE),
|
|
7317
|
-
fromTemplate("security_review", OPERATIONAL_TEMPLATE),
|
|
7318
|
-
fromTemplate("qa_session", OPERATIONAL_TEMPLATE),
|
|
7319
|
-
fromTemplate("a11y_audit", OPERATIONAL_TEMPLATE),
|
|
7320
|
-
fromTemplate("test_environment", OPERATIONAL_TEMPLATE),
|
|
7321
|
-
fromTemplate("workspace", OPERATIONAL_TEMPLATE),
|
|
7322
|
-
fromTemplate("pricing_strategy", OPERATIONAL_TEMPLATE),
|
|
7323
|
-
fromTemplate("discount_strategy", OPERATIONAL_TEMPLATE),
|
|
7324
|
-
// ── Phase C: APPROVAL (proposed / reviewing / approved / rejected / deprecated)
|
|
7325
|
-
fromTemplate("data_contract", APPROVAL_TEMPLATE),
|
|
7326
|
-
fromTemplate("audit_log_policy", APPROVAL_TEMPLATE),
|
|
7327
|
-
fromTemplate("access_policy", APPROVAL_TEMPLATE),
|
|
7328
|
-
fromTemplate("privacy_policy", APPROVAL_TEMPLATE),
|
|
7329
|
-
// ── Phase C: MATURITY (alpha / beta / ga / deprecated) ──────────────────────
|
|
7330
|
-
// screen removed (UPG-690 Q3, 0.21.0): hand-authored SCREEN_LIFECYCLE below —
|
|
7331
|
-
// the build-pipeline IS the real lifecycle, MATURITY was the mis-fit.
|
|
7332
|
-
fromTemplate("design_system", MATURITY_TEMPLATE),
|
|
7333
|
-
fromTemplate("design_component", MATURITY_TEMPLATE),
|
|
7334
|
-
fromTemplate("design_pattern", MATURITY_TEMPLATE),
|
|
7335
|
-
fromTemplate("brand_logo", MATURITY_TEMPLATE),
|
|
7336
|
-
fromTemplate("certification", MATURITY_TEMPLATE),
|
|
7337
|
-
fromTemplate("data_source", MATURITY_TEMPLATE),
|
|
7338
|
-
fromTemplate("data_product", MATURITY_TEMPLATE),
|
|
7339
|
-
fromTemplate("infrastructure_component", MATURITY_TEMPLATE),
|
|
7340
|
-
// ── Phase C: WORK_ITEM (todo / in_progress / in_review / done) ──────────────
|
|
7341
|
-
fromTemplate("agent_task", WORK_ITEM_TEMPLATE),
|
|
7342
|
-
// agent_skill and agent_hook moved WORK_ITEM -> OPERATIONAL at 0.33.0; see the
|
|
7343
|
-
// OPERATIONAL block below. agent_task stays: a task IS a work item.
|
|
7344
|
-
// UPG-690 (0.21.0): task (exact match) + epic (todo→in_progress→done ⊆ WORK_ITEM,
|
|
7345
|
-
// in_review becomes available — non-breaking widening) folded off hand-authored.
|
|
7346
|
-
fromTemplate("task", WORK_ITEM_TEMPLATE),
|
|
7347
|
-
fromTemplate("epic", WORK_ITEM_TEMPLATE),
|
|
7348
|
-
// ── Phase B: RISK_ITEM (identified → assessed → mitigated/accepted/closed) ──
|
|
7349
|
-
fromTemplate("threat", RISK_ITEM_TEMPLATE),
|
|
7350
|
-
fromTemplate("risk", RISK_ITEM_TEMPLATE),
|
|
7351
|
-
fromTemplate("compliance_requirement", RISK_ITEM_TEMPLATE),
|
|
7352
|
-
// ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
|
|
7353
|
-
fromTemplate("deal", SALES_DEAL_TEMPLATE),
|
|
7354
|
-
// ── STUDY (planned → running → analysing → complete / abandoned) ───────────
|
|
7355
|
-
// UPG-690 (0.21.0): time-boxed investigations folded off bespoke singletons.
|
|
7356
|
-
// `model_comparison` stays bespoke — its publish/archive tail is a weak fit.
|
|
7357
|
-
fromTemplate("experiment", STUDY_TEMPLATE),
|
|
7358
|
-
fromTemplate("experiment_run", STUDY_TEMPLATE),
|
|
7359
|
-
fromTemplate("research_study", STUDY_TEMPLATE),
|
|
7360
|
-
fromTemplate("design_sprint", STUDY_TEMPLATE),
|
|
7361
|
-
fromTemplate("feasibility_study", STUDY_TEMPLATE),
|
|
7362
|
-
fromTemplate("ai_experiment", STUDY_TEMPLATE),
|
|
7363
|
-
fromTemplate("eval_run", STUDY_TEMPLATE),
|
|
7364
|
-
// ── VALIDATION (untested → testing → validated / invalidated → archived) ───
|
|
7365
|
-
// UPG-690 (0.21.0): claims-tested-to-a-verdict folded off bespoke singletons.
|
|
7366
|
-
// `business_model` stays bespoke — its `pivoted` terminal is a strategic
|
|
7367
|
-
// outcome, not a verdict.
|
|
7368
|
-
fromTemplate("assumption", VALIDATION_TEMPLATE),
|
|
7369
|
-
fromTemplate("prototype", VALIDATION_TEMPLATE),
|
|
7370
|
-
fromTemplate("value_proposition", VALIDATION_TEMPLATE),
|
|
7371
|
-
fromTemplate("hypothesis", VALIDATION_TEMPLATE),
|
|
7372
|
-
// ── INCIDENT (open → triaged → in_progress → resolved / closed / wont_fix) ─
|
|
7373
|
-
// UPG-690 (0.21.0): triage-to-resolution defect/ticket family folded off
|
|
7374
|
-
// bespoke singletons. `incident` stays bespoke (richer SRE flow).
|
|
7375
|
-
fromTemplate("support_ticket", INCIDENT_TEMPLATE),
|
|
7376
|
-
fromTemplate("bug", INCIDENT_TEMPLATE),
|
|
7377
|
-
fromTemplate("a11y_issue", INCIDENT_TEMPLATE),
|
|
7378
|
-
fromTemplate("vulnerability", INCIDENT_TEMPLATE),
|
|
7379
|
-
fromTemplate("hallucination_report", INCIDENT_TEMPLATE),
|
|
7380
|
-
fromTemplate("technical_debt_item", INCIDENT_TEMPLATE),
|
|
7381
|
-
fromTemplate("customer_feedback", INCIDENT_TEMPLATE)
|
|
7382
|
-
];
|
|
7383
|
-
var FRAMEWORK_EXERCISE_LIFECYCLE = {
|
|
7384
|
-
entity_type: "framework_exercise",
|
|
7385
|
-
initial_phase: "draft",
|
|
7386
|
-
terminal_phases: ["archived"],
|
|
7387
|
-
phases: [
|
|
7388
|
-
{
|
|
7389
|
-
id: "draft",
|
|
7390
|
-
status_category: "started",
|
|
7391
|
-
label: "Draft",
|
|
7392
|
-
description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
|
|
7393
|
-
transitions_to: ["active", "archived"]
|
|
7394
|
-
},
|
|
7395
|
-
{
|
|
7396
|
-
id: "active",
|
|
7397
|
-
status_category: "started",
|
|
7398
|
-
label: "Active",
|
|
7399
|
-
description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
|
|
7400
|
-
transitions_to: ["archived"]
|
|
7401
|
-
},
|
|
7402
|
-
{
|
|
7403
|
-
id: "archived",
|
|
7404
|
-
status_category: "completed",
|
|
7405
|
-
label: "Archived",
|
|
7406
|
-
description: "A past run, retained for provenance. Still queryable but superseded by a newer exercise and hidden from default views. Can be revived to active.",
|
|
7407
|
-
transitions_to: ["active"]
|
|
7408
|
-
}
|
|
7409
|
-
]
|
|
7410
|
-
};
|
|
7411
|
-
var COMPOSITION_LIFECYCLE = {
|
|
7412
|
-
entity_type: "composition",
|
|
7413
|
-
initial_phase: "draft",
|
|
7414
|
-
terminal_phases: ["archived"],
|
|
7415
|
-
phases: [
|
|
7416
|
-
{
|
|
7417
|
-
id: "draft",
|
|
7418
|
-
status_category: "started",
|
|
7419
|
-
label: "Draft",
|
|
7420
|
-
description: "Being arranged. It has a slug reserved and members placed, but has never been published, so nothing resolves at its address yet.",
|
|
7421
|
-
transitions_to: ["published", "archived"]
|
|
7422
|
-
},
|
|
7423
|
-
{
|
|
7424
|
-
id: "published",
|
|
7425
|
-
status_category: "completed",
|
|
7426
|
-
label: "Published",
|
|
7427
|
-
description: "Live at its slug and linkable. Republishing the same slug stays in this phase and bumps the revision rather than moving the composition.",
|
|
7428
|
-
transitions_to: ["archived", "draft"]
|
|
7429
|
-
},
|
|
7430
|
-
{
|
|
7431
|
-
id: "archived",
|
|
7432
|
-
status_category: "completed",
|
|
7433
|
-
label: "Archived",
|
|
7434
|
-
description: "Withdrawn from the published set but retained, so an existing link resolves to a view marked out of date rather than to nothing. Can be republished.",
|
|
7435
|
-
transitions_to: ["published", "draft"]
|
|
7436
|
-
}
|
|
7437
|
-
]
|
|
7438
|
-
};
|
|
7439
|
-
var SPECIFICATION_LIFECYCLE = {
|
|
7440
|
-
entity_type: "specification",
|
|
7441
|
-
initial_phase: "draft",
|
|
7442
|
-
terminal_phases: ["superseded"],
|
|
7443
|
-
phases: [
|
|
7444
|
-
{
|
|
7445
|
-
id: "draft",
|
|
7446
|
-
status_category: "started",
|
|
7447
|
-
label: "Draft",
|
|
7448
|
-
description: "Being authored. Not yet adopted as the canonical specification.",
|
|
7449
|
-
transitions_to: ["active", "superseded"]
|
|
7450
|
-
},
|
|
7451
|
-
{
|
|
7452
|
-
id: "active",
|
|
7453
|
-
status_category: "started",
|
|
7454
|
-
label: "Active",
|
|
7455
|
-
description: "In force. The current authoritative version products implement and conform to.",
|
|
7456
|
-
transitions_to: ["deprecated", "superseded"]
|
|
7457
|
-
},
|
|
7458
|
-
{
|
|
7459
|
-
id: "deprecated",
|
|
7460
|
-
status_category: "started",
|
|
7461
|
-
label: "Deprecated",
|
|
7462
|
-
description: "Still valid but no longer recommended; a successor exists or is imminent.",
|
|
7463
|
-
transitions_to: ["superseded"]
|
|
7464
|
-
},
|
|
7465
|
-
{
|
|
7466
|
-
id: "superseded",
|
|
7467
|
-
status_category: "completed",
|
|
7468
|
-
label: "Superseded",
|
|
7469
|
-
description: "Replaced by a newer specification. Retained for historical reference.",
|
|
7470
|
-
transitions_to: []
|
|
7471
|
-
}
|
|
7472
|
-
]
|
|
7473
|
-
};
|
|
7474
|
-
var PLANNING_CYCLE_LIFECYCLE = {
|
|
7475
|
-
entity_type: "planning_cycle",
|
|
7476
|
-
initial_phase: "planned",
|
|
7477
|
-
terminal_phases: ["closed"],
|
|
7478
|
-
phases: [
|
|
7479
|
-
{ id: "planned", status_category: "unstarted", label: "Planned", description: "The cycle is being shaped: dates set, goal written, work scheduled into it. It has not started yet.", transitions_to: ["active"] },
|
|
7480
|
-
{ id: "active", status_category: "started", label: "Active", description: "The team is inside the interval; work is flowing through it.", transitions_to: ["closed"] },
|
|
7481
|
-
{ id: "closed", status_category: "completed", label: "Closed", description: "The interval has ended. Scheduled work has shipped, carried over, or been dropped. May reopen if the team extends or reruns it.", transitions_to: ["active"] }
|
|
7482
|
-
]
|
|
7483
|
-
};
|
|
7484
|
-
var UPG_LIFECYCLES = [
|
|
7485
|
-
// Product (root)
|
|
7486
|
-
PRODUCT_LIFECYCLE,
|
|
7487
|
-
// Foundations (0.9.12)
|
|
7488
|
-
SPECIFICATION_LIFECYCLE,
|
|
7489
|
-
// Discovery & Validation
|
|
7490
|
-
NEED_LIFECYCLE,
|
|
7491
|
-
OPPORTUNITY_LIFECYCLE,
|
|
7492
|
-
SOLUTION_LIFECYCLE,
|
|
7493
|
-
EXPERIMENT_PLAN_LIFECYCLE,
|
|
7494
|
-
RESEARCH_PLAN_LIFECYCLE,
|
|
7495
|
-
FEEDBACK_PROGRAM_LIFECYCLE,
|
|
7496
|
-
FEATURE_REQUEST_LIFECYCLE,
|
|
7497
|
-
BETA_PROGRAM_LIFECYCLE,
|
|
7498
|
-
USER_ADVISORY_BOARD_LIFECYCLE,
|
|
7499
|
-
// Strategy & Product Specification
|
|
7500
|
-
OUTCOME_LIFECYCLE,
|
|
7501
|
-
VISION_LIFECYCLE,
|
|
7502
|
-
MISSION_LIFECYCLE,
|
|
7503
|
-
CAPABILITY_LIFECYCLE,
|
|
7504
|
-
OBJECTIVE_LIFECYCLE,
|
|
7505
|
-
KEY_RESULT_LIFECYCLE,
|
|
7506
|
-
STRATEGIC_THEME_LIFECYCLE,
|
|
7507
|
-
INITIATIVE_LIFECYCLE,
|
|
7508
|
-
STRATEGIC_PILLAR_LIFECYCLE,
|
|
7509
|
-
STRATEGIC_QUESTION_LIFECYCLE,
|
|
7510
|
-
FEATURE_AREA_LIFECYCLE,
|
|
7511
|
-
FEATURE_LIFECYCLE,
|
|
7512
|
-
// user_story is lifecycle-free (declared in UPG_LIFECYCLE_FREE_TYPES below).
|
|
7513
|
-
// story_task lifecycle removed v0.4.0; collapsed into task. task + epic are
|
|
7514
|
-
// now WORK_ITEM-template-derived (UPG-690, 0.21.0) — see TEMPLATE_LIFECYCLES.
|
|
7515
|
-
RELEASE_LIFECYCLE,
|
|
7516
|
-
ROADMAP_ITEM_LIFECYCLE,
|
|
7517
|
-
PLANNING_CYCLE_LIFECYCLE,
|
|
7518
|
-
IP_ASSET_LIFECYCLE,
|
|
7519
|
-
CONTRACT_LIFECYCLE,
|
|
7520
|
-
DESIGN_CONCEPT_LIFECYCLE,
|
|
7521
|
-
BRAND_IDENTITY_LIFECYCLE,
|
|
7522
|
-
SCREEN_LIFECYCLE,
|
|
7523
|
-
SURFACE_LIFECYCLE,
|
|
7524
|
-
// Engineering & Operations
|
|
7525
|
-
SERVICE_LIFECYCLE,
|
|
7526
|
-
DEPLOYMENT_LIFECYCLE,
|
|
7527
|
-
FEATURE_FLAG_LIFECYCLE,
|
|
7528
|
-
INVESTIGATION_LIFECYCLE,
|
|
7529
|
-
EXTERNAL_API_LIFECYCLE,
|
|
7530
|
-
DATABASE_SCHEMA_LIFECYCLE,
|
|
7531
|
-
INCIDENT_LIFECYCLE,
|
|
7532
|
-
SECURITY_CONTROL_LIFECYCLE,
|
|
7533
|
-
DATA_PIPELINE_LIFECYCLE,
|
|
7534
|
-
AI_MODEL_LIFECYCLE,
|
|
7535
|
-
WORKFLOW_RUN_LIFECYCLE,
|
|
7536
|
-
AGENT_DEFINITION_LIFECYCLE,
|
|
7537
|
-
AGENT_SESSION_LIFECYCLE,
|
|
7538
|
-
REVIEW_GATE_LIFECYCLE,
|
|
7539
|
-
TEST_SUITE_LIFECYCLE,
|
|
7540
|
-
TEST_CASE_LIFECYCLE,
|
|
7541
|
-
// ── Phase B hand-authored ─────────────────────────────────────────
|
|
7542
|
-
// Research + Validation
|
|
7543
|
-
INSIGHT_LIFECYCLE,
|
|
7544
|
-
RESEARCH_QUESTION_LIFECYCLE,
|
|
7545
|
-
INTERVIEW_GUIDE_LIFECYCLE,
|
|
7546
|
-
TEST_PLAN_LIFECYCLE,
|
|
7547
|
-
// AI workflow
|
|
7548
|
-
AI_DATASET_LIFECYCLE,
|
|
7549
|
-
AI_GUARDRAIL_LIFECYCLE,
|
|
7550
|
-
MODEL_COMPARISON_LIFECYCLE,
|
|
7551
|
-
PROMPT_VERSION_LIFECYCLE,
|
|
7552
|
-
EVAL_BENCHMARK_LIFECYCLE,
|
|
7553
|
-
// Business Model
|
|
7554
|
-
BUSINESS_MODEL_LIFECYCLE,
|
|
7555
|
-
REVENUE_STREAM_LIFECYCLE,
|
|
7556
|
-
// Customer Success
|
|
7557
|
-
CUSTOMER_HEALTH_SCORE_LIFECYCLE,
|
|
7558
|
-
PLAYBOOK_LIFECYCLE,
|
|
7559
|
-
// Team & Organisation
|
|
7560
|
-
TEAM_OKR_LIFECYCLE,
|
|
7561
|
-
RETROSPECTIVE_LIFECYCLE,
|
|
7562
|
-
DEPENDENCY_LIFECYCLE,
|
|
7563
|
-
ROLE_LIFECYCLE,
|
|
7564
|
-
CAPACITY_PLAN_LIFECYCLE,
|
|
7565
|
-
// Product & Growth
|
|
7566
|
-
VARIANT_LIFECYCLE,
|
|
7567
|
-
GROWTH_CAMPAIGN_LIFECYCLE,
|
|
7568
|
-
// Workspace
|
|
7569
|
-
FRAMEWORK_EXERCISE_LIFECYCLE,
|
|
7570
|
-
COMPOSITION_LIFECYCLE,
|
|
7571
|
-
// ── Template-generated lifecycles ─────────────────────────────────
|
|
7572
|
-
...TEMPLATE_LIFECYCLES
|
|
7573
|
-
];
|
|
7574
|
-
function getLifecycleForType(entityType) {
|
|
7575
|
-
return UPG_LIFECYCLES.find((l) => l.entity_type === entityType);
|
|
7576
|
-
}
|
|
7577
|
-
var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
7578
|
-
// ── Foundations (3 of 4): a primitive either exists or is deprecated; no
|
|
7579
|
-
// phase ladder (like metric). `operating_lifecycle`/`operating_stage` are
|
|
7580
|
-
// static ordered reference data (the loop is a `cyclic` flag, not a state
|
|
7581
|
-
// machine). `specification` carries a lifecycle. ──
|
|
7582
|
-
"primitive",
|
|
7583
|
-
"operating_lifecycle",
|
|
7584
|
-
"operating_stage",
|
|
7585
|
-
// ── User (5): personas and JTBD atoms are reference data ──────────────────
|
|
7586
|
-
"persona",
|
|
7587
|
-
"job",
|
|
7588
|
-
"job_step",
|
|
7589
|
-
"desired_outcome",
|
|
7590
|
-
"switching_cost",
|
|
7591
|
-
// ── Market Intelligence (7 of 8): competitor landscape is reference data;
|
|
7592
|
-
// competitor_signal is an append-only dated event (no phase ladder);
|
|
7593
|
-
// competitive_analysis shipped PUBLISHING lifecycle in Phase C;
|
|
7594
|
-
// classification_axis and classification_value are reference taxonomy
|
|
7595
|
-
// atoms with no state machine. ─
|
|
7596
|
-
"competitor",
|
|
7597
|
-
"competitor_feature",
|
|
7598
|
-
"competitor_signal",
|
|
7599
|
-
"market_trend",
|
|
7600
|
-
"market_segment",
|
|
7601
|
-
"classification_axis",
|
|
7602
|
-
"classification_value",
|
|
7603
|
-
// ── Product Specification (1): a configuration_axis is a structural
|
|
7604
|
-
// DECLARATION (a lever and its closed value set), not a thing that
|
|
7605
|
-
// progresses through states. Same reading as classification_axis above.
|
|
7606
|
-
"configuration_axis",
|
|
7607
|
-
// ── User Research (5 of 8): participants, quotes, observations,
|
|
7608
|
-
// clusters, survey responses are immutable data points ────────────────
|
|
7609
|
-
"participant",
|
|
7610
|
-
"observation",
|
|
7611
|
-
"quote",
|
|
7612
|
-
"affinity_cluster",
|
|
7613
|
-
"survey_response",
|
|
7614
|
-
// ── UX Design (3 of 8): journey structural atoms ──────────────────────────
|
|
7615
|
-
"journey_step",
|
|
7616
|
-
"journey_phase",
|
|
7617
|
-
"journey_action",
|
|
7618
|
-
"screen_state",
|
|
7619
|
-
// ── Design System (3 of 7): tokens and annotations are atomic values ────
|
|
7620
|
-
"design_token",
|
|
7621
|
-
"annotation",
|
|
7622
|
-
// ── Brand (4 of 6): colour/typography/imagery/voice are reference atoms ──
|
|
7623
|
-
"brand_colour",
|
|
7624
|
-
"brand_typography",
|
|
7625
|
-
"brand_imagery",
|
|
7626
|
-
// ── Product Specification (4 of 5): criteria, grouping, historical, +
|
|
7627
|
-
// user_story (the templated promise is a stable design artefact; the paired
|
|
7628
|
-
// task carries the lifecycle). Re-canon story_statement → user_story at
|
|
7629
|
-
// v0.7.0/UPG-571. ─
|
|
7630
|
-
"acceptance_criterion",
|
|
7631
|
-
"changelog",
|
|
7632
|
-
"roadmap_theme",
|
|
7633
|
-
"user_story",
|
|
7634
|
-
// ── Strategy: metric is a measurement definition; metric_quality_assessment
|
|
7635
|
-
// is a point-in-time snapshot; value_stream is a mapped flow;
|
|
7636
|
-
// constraint carries its own `constraint_status` enum (binding/advisory/
|
|
7637
|
-
// lifted) as a property; base-node `status` not used.
|
|
7638
|
-
// vision/mission/capability/outcome get lifecycles in Phase B or C ─────
|
|
7639
|
-
"metric",
|
|
7640
|
-
"metric_quality_assessment",
|
|
7641
|
-
"value_stream",
|
|
7642
|
-
"constraint",
|
|
7643
|
-
// ── Engineering (15 of 17): DDD modelling primitives + infra references.
|
|
7644
|
-
// root_cause / symptom / fix are investigation findings, not workflows
|
|
7645
|
-
// (investigation itself has a lifecycle) ────────────────────────────────
|
|
7646
|
-
"aggregate",
|
|
7647
|
-
"domain_entity",
|
|
7648
|
-
"value_object",
|
|
7649
|
-
"command",
|
|
7650
|
-
"read_model",
|
|
7651
|
-
"domain_event",
|
|
7652
|
-
"bounded_context",
|
|
7653
|
-
"api_endpoint",
|
|
7654
|
-
"queue_topic",
|
|
7655
|
-
"build_artifact",
|
|
7656
|
-
"code_repository",
|
|
7657
|
-
"library_dependency",
|
|
7658
|
-
"integration_pattern",
|
|
7659
|
-
"data_flow",
|
|
7660
|
-
"root_cause",
|
|
7661
|
-
"symptom",
|
|
7662
|
-
"fix",
|
|
7663
|
-
// ── Business Model (6 of 9): BMC cells are structural frames ────────────
|
|
7664
|
-
"cost_structure",
|
|
7665
|
-
"unit_economics",
|
|
7666
|
-
"key_resource",
|
|
7667
|
-
"key_activity",
|
|
7668
|
-
"customer_relationship",
|
|
7669
|
-
"distribution_channel",
|
|
7670
|
-
// ── Go-To-Market (8 of 13): positioning/ICP/territory/sales_motion are
|
|
7671
|
-
// configuration; objection/rebuttal/proof_point are catalog entries ───
|
|
7672
|
-
"ideal_customer_profile",
|
|
7673
|
-
"positioning",
|
|
7674
|
-
"sales_motion",
|
|
7675
|
-
"territory",
|
|
7676
|
-
"objection",
|
|
7677
|
-
"rebuttal",
|
|
7678
|
-
"proof_point",
|
|
7679
|
-
// ── Team & Organisation (7 of 11): org structural atoms ───────────────────
|
|
7680
|
-
"team",
|
|
7681
|
-
"stakeholder",
|
|
6367
|
+
// NOT lifecycle-free. The `user_story` precedent for lifecycle-freedom is the
|
|
6368
|
+
// Statement/Implementation split, where the paired `task` carries the
|
|
6369
|
+
// lifecycle. There is no paired entity here, so lifecycle-freedom would leave
|
|
6370
|
+
// `agent_skill` unable to say it is disabled and would entrench
|
|
6371
|
+
// `AgentHookProperties.hook_status` (now @deprecated) as the permanent status
|
|
6372
|
+
// axis, which is the *_status shadow Pattern D collapsed fourteen times.
|
|
6373
|
+
fromTemplate("agent_skill", OPERATIONAL_TEMPLATE),
|
|
6374
|
+
fromTemplate("agent_hook", OPERATIONAL_TEMPLATE),
|
|
6375
|
+
fromTemplate("on_call_rotation", OPERATIONAL_TEMPLATE),
|
|
6376
|
+
fromTemplate("security_audit", OPERATIONAL_TEMPLATE),
|
|
6377
|
+
fromTemplate("penetration_test", OPERATIONAL_TEMPLATE),
|
|
6378
|
+
fromTemplate("security_review", OPERATIONAL_TEMPLATE),
|
|
6379
|
+
fromTemplate("qa_session", OPERATIONAL_TEMPLATE),
|
|
6380
|
+
fromTemplate("a11y_audit", OPERATIONAL_TEMPLATE),
|
|
6381
|
+
fromTemplate("test_environment", OPERATIONAL_TEMPLATE),
|
|
6382
|
+
fromTemplate("workspace", OPERATIONAL_TEMPLATE),
|
|
6383
|
+
fromTemplate("pricing_strategy", OPERATIONAL_TEMPLATE),
|
|
6384
|
+
fromTemplate("discount_strategy", OPERATIONAL_TEMPLATE),
|
|
6385
|
+
// ── Phase C: APPROVAL (proposed / reviewing / approved / rejected / deprecated)
|
|
6386
|
+
fromTemplate("data_contract", APPROVAL_TEMPLATE),
|
|
6387
|
+
fromTemplate("audit_log_policy", APPROVAL_TEMPLATE),
|
|
6388
|
+
fromTemplate("access_policy", APPROVAL_TEMPLATE),
|
|
6389
|
+
fromTemplate("privacy_policy", APPROVAL_TEMPLATE),
|
|
6390
|
+
// ── Phase C: MATURITY (alpha / beta / ga / deprecated) ──────────────────────
|
|
6391
|
+
// screen removed (UPG-690 Q3, 0.21.0): hand-authored SCREEN_LIFECYCLE below —
|
|
6392
|
+
// the build-pipeline IS the real lifecycle, MATURITY was the mis-fit.
|
|
6393
|
+
fromTemplate("design_system", MATURITY_TEMPLATE),
|
|
6394
|
+
fromTemplate("design_component", MATURITY_TEMPLATE),
|
|
6395
|
+
fromTemplate("design_pattern", MATURITY_TEMPLATE),
|
|
6396
|
+
fromTemplate("brand_logo", MATURITY_TEMPLATE),
|
|
6397
|
+
fromTemplate("certification", MATURITY_TEMPLATE),
|
|
6398
|
+
fromTemplate("data_source", MATURITY_TEMPLATE),
|
|
6399
|
+
fromTemplate("data_product", MATURITY_TEMPLATE),
|
|
6400
|
+
fromTemplate("infrastructure_component", MATURITY_TEMPLATE),
|
|
6401
|
+
// ── Phase C: WORK_ITEM (todo / in_progress / in_review / done) ──────────────
|
|
6402
|
+
fromTemplate("agent_task", WORK_ITEM_TEMPLATE),
|
|
6403
|
+
// agent_skill and agent_hook moved WORK_ITEM -> OPERATIONAL at 0.33.0; see the
|
|
6404
|
+
// OPERATIONAL block below. agent_task stays: a task IS a work item.
|
|
6405
|
+
// UPG-690 (0.21.0): task (exact match) + epic (todo→in_progress→done ⊆ WORK_ITEM,
|
|
6406
|
+
// in_review becomes available — non-breaking widening) folded off hand-authored.
|
|
6407
|
+
fromTemplate("task", WORK_ITEM_TEMPLATE),
|
|
6408
|
+
fromTemplate("epic", WORK_ITEM_TEMPLATE),
|
|
6409
|
+
// ── Phase B: RISK_ITEM (identified → assessed → mitigated/accepted/closed) ──
|
|
6410
|
+
fromTemplate("threat", RISK_ITEM_TEMPLATE),
|
|
6411
|
+
fromTemplate("risk", RISK_ITEM_TEMPLATE),
|
|
6412
|
+
fromTemplate("compliance_requirement", RISK_ITEM_TEMPLATE),
|
|
6413
|
+
// ── Phase B: SALES_DEAL (qualified → proposal → negotiation → won/lost) ─────
|
|
6414
|
+
fromTemplate("deal", SALES_DEAL_TEMPLATE),
|
|
6415
|
+
// ── STUDY (planned → running → analysing → complete / abandoned) ───────────
|
|
6416
|
+
// UPG-690 (0.21.0): time-boxed investigations folded off bespoke singletons.
|
|
6417
|
+
// `model_comparison` stays bespoke — its publish/archive tail is a weak fit.
|
|
6418
|
+
fromTemplate("experiment", STUDY_TEMPLATE),
|
|
6419
|
+
fromTemplate("experiment_run", STUDY_TEMPLATE),
|
|
6420
|
+
fromTemplate("research_study", STUDY_TEMPLATE),
|
|
6421
|
+
fromTemplate("design_sprint", STUDY_TEMPLATE),
|
|
6422
|
+
fromTemplate("feasibility_study", STUDY_TEMPLATE),
|
|
6423
|
+
fromTemplate("ai_experiment", STUDY_TEMPLATE),
|
|
6424
|
+
fromTemplate("eval_run", STUDY_TEMPLATE),
|
|
6425
|
+
// ── VALIDATION (untested → testing → validated / invalidated → archived) ───
|
|
6426
|
+
// UPG-690 (0.21.0): claims-tested-to-a-verdict folded off bespoke singletons.
|
|
6427
|
+
// `business_model` stays bespoke — its `pivoted` terminal is a strategic
|
|
6428
|
+
// outcome, not a verdict.
|
|
6429
|
+
fromTemplate("assumption", VALIDATION_TEMPLATE),
|
|
6430
|
+
fromTemplate("prototype", VALIDATION_TEMPLATE),
|
|
6431
|
+
fromTemplate("value_proposition", VALIDATION_TEMPLATE),
|
|
6432
|
+
fromTemplate("hypothesis", VALIDATION_TEMPLATE),
|
|
6433
|
+
// ── INCIDENT (open → triaged → in_progress → resolved / closed / wont_fix) ─
|
|
6434
|
+
// UPG-690 (0.21.0): triage-to-resolution defect/ticket family folded off
|
|
6435
|
+
// bespoke singletons. `incident` stays bespoke (richer SRE flow).
|
|
6436
|
+
fromTemplate("support_ticket", INCIDENT_TEMPLATE),
|
|
6437
|
+
fromTemplate("bug", INCIDENT_TEMPLATE),
|
|
6438
|
+
fromTemplate("a11y_issue", INCIDENT_TEMPLATE),
|
|
6439
|
+
fromTemplate("vulnerability", INCIDENT_TEMPLATE),
|
|
6440
|
+
fromTemplate("hallucination_report", INCIDENT_TEMPLATE),
|
|
6441
|
+
fromTemplate("technical_debt_item", INCIDENT_TEMPLATE),
|
|
6442
|
+
fromTemplate("customer_feedback", INCIDENT_TEMPLATE)
|
|
6443
|
+
];
|
|
6444
|
+
var FRAMEWORK_EXERCISE_LIFECYCLE = {
|
|
6445
|
+
entity_type: "framework_exercise",
|
|
6446
|
+
initial_phase: "draft",
|
|
6447
|
+
terminal_phases: ["archived"],
|
|
6448
|
+
phases: [
|
|
6449
|
+
{
|
|
6450
|
+
id: "draft",
|
|
6451
|
+
status_category: "started",
|
|
6452
|
+
label: "Draft",
|
|
6453
|
+
description: "The exercise has been created and entities pulled into scope, but the framework's inputs have not all been filled in yet.",
|
|
6454
|
+
transitions_to: ["active", "archived"]
|
|
6455
|
+
},
|
|
6456
|
+
{
|
|
6457
|
+
id: "active",
|
|
6458
|
+
status_category: "started",
|
|
6459
|
+
label: "Active",
|
|
6460
|
+
description: "The current, authoritative run of its framework. Its include edges carry the live results consumers read, rank, and render by.",
|
|
6461
|
+
transitions_to: ["archived"]
|
|
6462
|
+
},
|
|
6463
|
+
{
|
|
6464
|
+
id: "archived",
|
|
6465
|
+
status_category: "completed",
|
|
6466
|
+
label: "Archived",
|
|
6467
|
+
description: "A past run, retained for provenance. Still queryable but superseded by a newer exercise and hidden from default views. Can be revived to active.",
|
|
6468
|
+
transitions_to: ["active"]
|
|
6469
|
+
}
|
|
6470
|
+
]
|
|
6471
|
+
};
|
|
6472
|
+
var COMPOSITION_LIFECYCLE = {
|
|
6473
|
+
entity_type: "composition",
|
|
6474
|
+
initial_phase: "draft",
|
|
6475
|
+
terminal_phases: ["archived"],
|
|
6476
|
+
phases: [
|
|
6477
|
+
{
|
|
6478
|
+
id: "draft",
|
|
6479
|
+
status_category: "started",
|
|
6480
|
+
label: "Draft",
|
|
6481
|
+
description: "Being arranged. It has a slug reserved and members placed, but has never been published, so nothing resolves at its address yet.",
|
|
6482
|
+
transitions_to: ["published", "archived"]
|
|
6483
|
+
},
|
|
6484
|
+
{
|
|
6485
|
+
id: "published",
|
|
6486
|
+
status_category: "completed",
|
|
6487
|
+
label: "Published",
|
|
6488
|
+
description: "Live at its slug and linkable. Republishing the same slug stays in this phase and bumps the revision rather than moving the composition.",
|
|
6489
|
+
transitions_to: ["archived", "draft"]
|
|
6490
|
+
},
|
|
6491
|
+
{
|
|
6492
|
+
id: "archived",
|
|
6493
|
+
status_category: "completed",
|
|
6494
|
+
label: "Archived",
|
|
6495
|
+
description: "Withdrawn from the published set but retained, so an existing link resolves to a view marked out of date rather than to nothing. Can be republished.",
|
|
6496
|
+
transitions_to: ["published", "draft"]
|
|
6497
|
+
}
|
|
6498
|
+
]
|
|
6499
|
+
};
|
|
6500
|
+
var SPECIFICATION_LIFECYCLE = {
|
|
6501
|
+
entity_type: "specification",
|
|
6502
|
+
initial_phase: "draft",
|
|
6503
|
+
terminal_phases: ["superseded"],
|
|
6504
|
+
phases: [
|
|
6505
|
+
{
|
|
6506
|
+
id: "draft",
|
|
6507
|
+
status_category: "started",
|
|
6508
|
+
label: "Draft",
|
|
6509
|
+
description: "Being authored. Not yet adopted as the canonical specification.",
|
|
6510
|
+
transitions_to: ["active", "superseded"]
|
|
6511
|
+
},
|
|
6512
|
+
{
|
|
6513
|
+
id: "active",
|
|
6514
|
+
status_category: "started",
|
|
6515
|
+
label: "Active",
|
|
6516
|
+
description: "In force. The current authoritative version products implement and conform to.",
|
|
6517
|
+
transitions_to: ["deprecated", "superseded"]
|
|
6518
|
+
},
|
|
6519
|
+
{
|
|
6520
|
+
id: "deprecated",
|
|
6521
|
+
status_category: "started",
|
|
6522
|
+
label: "Deprecated",
|
|
6523
|
+
description: "Still valid but no longer recommended; a successor exists or is imminent.",
|
|
6524
|
+
transitions_to: ["superseded"]
|
|
6525
|
+
},
|
|
6526
|
+
{
|
|
6527
|
+
id: "superseded",
|
|
6528
|
+
status_category: "completed",
|
|
6529
|
+
label: "Superseded",
|
|
6530
|
+
description: "Replaced by a newer specification. Retained for historical reference.",
|
|
6531
|
+
transitions_to: []
|
|
6532
|
+
}
|
|
6533
|
+
]
|
|
6534
|
+
};
|
|
6535
|
+
var PLANNING_CYCLE_LIFECYCLE = {
|
|
6536
|
+
entity_type: "planning_cycle",
|
|
6537
|
+
initial_phase: "planned",
|
|
6538
|
+
terminal_phases: ["closed"],
|
|
6539
|
+
phases: [
|
|
6540
|
+
{ id: "planned", status_category: "unstarted", label: "Planned", description: "The cycle is being shaped: dates set, goal written, work scheduled into it. It has not started yet.", transitions_to: ["active"] },
|
|
6541
|
+
{ id: "active", status_category: "started", label: "Active", description: "The team is inside the interval; work is flowing through it.", transitions_to: ["closed"] },
|
|
6542
|
+
{ id: "closed", status_category: "completed", label: "Closed", description: "The interval has ended. Scheduled work has shipped, carried over, or been dropped. May reopen if the team extends or reruns it.", transitions_to: ["active"] }
|
|
6543
|
+
]
|
|
6544
|
+
};
|
|
6545
|
+
var UPG_LIFECYCLES = [
|
|
6546
|
+
// Product (root)
|
|
6547
|
+
PRODUCT_LIFECYCLE,
|
|
6548
|
+
// Foundations (0.9.12)
|
|
6549
|
+
SPECIFICATION_LIFECYCLE,
|
|
6550
|
+
// Discovery & Validation
|
|
6551
|
+
NEED_LIFECYCLE,
|
|
6552
|
+
OPPORTUNITY_LIFECYCLE,
|
|
6553
|
+
SOLUTION_LIFECYCLE,
|
|
6554
|
+
EXPERIMENT_PLAN_LIFECYCLE,
|
|
6555
|
+
RESEARCH_PLAN_LIFECYCLE,
|
|
6556
|
+
FEEDBACK_PROGRAM_LIFECYCLE,
|
|
6557
|
+
FEATURE_REQUEST_LIFECYCLE,
|
|
6558
|
+
BETA_PROGRAM_LIFECYCLE,
|
|
6559
|
+
USER_ADVISORY_BOARD_LIFECYCLE,
|
|
6560
|
+
// Strategy & Product Specification
|
|
6561
|
+
OUTCOME_LIFECYCLE,
|
|
6562
|
+
VISION_LIFECYCLE,
|
|
6563
|
+
MISSION_LIFECYCLE,
|
|
6564
|
+
CAPABILITY_LIFECYCLE,
|
|
6565
|
+
OBJECTIVE_LIFECYCLE,
|
|
6566
|
+
KEY_RESULT_LIFECYCLE,
|
|
6567
|
+
STRATEGIC_THEME_LIFECYCLE,
|
|
6568
|
+
INITIATIVE_LIFECYCLE,
|
|
6569
|
+
STRATEGIC_PILLAR_LIFECYCLE,
|
|
6570
|
+
STRATEGIC_QUESTION_LIFECYCLE,
|
|
6571
|
+
FEATURE_AREA_LIFECYCLE,
|
|
6572
|
+
FEATURE_LIFECYCLE,
|
|
6573
|
+
// user_story is lifecycle-free (declared in UPG_LIFECYCLE_FREE_TYPES below).
|
|
6574
|
+
// story_task lifecycle removed v0.4.0; collapsed into task. task + epic are
|
|
6575
|
+
// now WORK_ITEM-template-derived (UPG-690, 0.21.0) — see TEMPLATE_LIFECYCLES.
|
|
6576
|
+
RELEASE_LIFECYCLE,
|
|
6577
|
+
ROADMAP_ITEM_LIFECYCLE,
|
|
6578
|
+
PLANNING_CYCLE_LIFECYCLE,
|
|
6579
|
+
IP_ASSET_LIFECYCLE,
|
|
6580
|
+
CONTRACT_LIFECYCLE,
|
|
6581
|
+
DESIGN_CONCEPT_LIFECYCLE,
|
|
6582
|
+
BRAND_IDENTITY_LIFECYCLE,
|
|
6583
|
+
SCREEN_LIFECYCLE,
|
|
6584
|
+
SURFACE_LIFECYCLE,
|
|
6585
|
+
// Engineering & Operations
|
|
6586
|
+
SERVICE_LIFECYCLE,
|
|
6587
|
+
DEPLOYMENT_LIFECYCLE,
|
|
6588
|
+
FEATURE_FLAG_LIFECYCLE,
|
|
6589
|
+
INVESTIGATION_LIFECYCLE,
|
|
6590
|
+
EXTERNAL_API_LIFECYCLE,
|
|
6591
|
+
DATABASE_SCHEMA_LIFECYCLE,
|
|
6592
|
+
INCIDENT_LIFECYCLE,
|
|
6593
|
+
SECURITY_CONTROL_LIFECYCLE,
|
|
6594
|
+
DATA_PIPELINE_LIFECYCLE,
|
|
6595
|
+
AI_MODEL_LIFECYCLE,
|
|
6596
|
+
WORKFLOW_RUN_LIFECYCLE,
|
|
6597
|
+
AGENT_DEFINITION_LIFECYCLE,
|
|
6598
|
+
AGENT_SESSION_LIFECYCLE,
|
|
6599
|
+
REVIEW_GATE_LIFECYCLE,
|
|
6600
|
+
TEST_SUITE_LIFECYCLE,
|
|
6601
|
+
TEST_CASE_LIFECYCLE,
|
|
6602
|
+
// ── Phase B hand-authored ─────────────────────────────────────────
|
|
6603
|
+
// Research + Validation
|
|
6604
|
+
INSIGHT_LIFECYCLE,
|
|
6605
|
+
RESEARCH_QUESTION_LIFECYCLE,
|
|
6606
|
+
INTERVIEW_GUIDE_LIFECYCLE,
|
|
6607
|
+
TEST_PLAN_LIFECYCLE,
|
|
6608
|
+
// AI workflow
|
|
6609
|
+
AI_DATASET_LIFECYCLE,
|
|
6610
|
+
AI_GUARDRAIL_LIFECYCLE,
|
|
6611
|
+
MODEL_COMPARISON_LIFECYCLE,
|
|
6612
|
+
PROMPT_VERSION_LIFECYCLE,
|
|
6613
|
+
EVAL_BENCHMARK_LIFECYCLE,
|
|
6614
|
+
// Business Model
|
|
6615
|
+
BUSINESS_MODEL_LIFECYCLE,
|
|
6616
|
+
REVENUE_STREAM_LIFECYCLE,
|
|
6617
|
+
// Customer Success
|
|
6618
|
+
CUSTOMER_HEALTH_SCORE_LIFECYCLE,
|
|
6619
|
+
PLAYBOOK_LIFECYCLE,
|
|
6620
|
+
// Team & Organisation
|
|
6621
|
+
TEAM_OKR_LIFECYCLE,
|
|
6622
|
+
RETROSPECTIVE_LIFECYCLE,
|
|
6623
|
+
DEPENDENCY_LIFECYCLE,
|
|
6624
|
+
ROLE_LIFECYCLE,
|
|
6625
|
+
CAPACITY_PLAN_LIFECYCLE,
|
|
6626
|
+
// Product & Growth
|
|
6627
|
+
VARIANT_LIFECYCLE,
|
|
6628
|
+
GROWTH_CAMPAIGN_LIFECYCLE,
|
|
6629
|
+
// Workspace
|
|
6630
|
+
FRAMEWORK_EXERCISE_LIFECYCLE,
|
|
6631
|
+
COMPOSITION_LIFECYCLE,
|
|
6632
|
+
// ── Template-generated lifecycles ─────────────────────────────────
|
|
6633
|
+
...TEMPLATE_LIFECYCLES
|
|
6634
|
+
];
|
|
6635
|
+
function getLifecycleForType(entityType) {
|
|
6636
|
+
return UPG_LIFECYCLES.find((l) => l.entity_type === entityType);
|
|
6637
|
+
}
|
|
6638
|
+
var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
6639
|
+
// ── Foundations (3 of 4): a primitive either exists or is deprecated; no
|
|
6640
|
+
// phase ladder (like metric). `operating_lifecycle`/`operating_stage` are
|
|
6641
|
+
// static ordered reference data (the loop is a `cyclic` flag, not a state
|
|
6642
|
+
// machine). `specification` carries a lifecycle. ──
|
|
6643
|
+
"primitive",
|
|
6644
|
+
"operating_lifecycle",
|
|
6645
|
+
"operating_stage",
|
|
6646
|
+
// ── User (5): personas and JTBD atoms are reference data ──────────────────
|
|
6647
|
+
"persona",
|
|
6648
|
+
"job",
|
|
6649
|
+
"job_step",
|
|
6650
|
+
"desired_outcome",
|
|
6651
|
+
"switching_cost",
|
|
6652
|
+
// ── Market Intelligence (7 of 8): competitor landscape is reference data;
|
|
6653
|
+
// competitor_signal is an append-only dated event (no phase ladder);
|
|
6654
|
+
// competitive_analysis shipped PUBLISHING lifecycle in Phase C;
|
|
6655
|
+
// classification_axis and classification_value are reference taxonomy
|
|
6656
|
+
// atoms with no state machine. ─
|
|
6657
|
+
"competitor",
|
|
6658
|
+
"competitor_feature",
|
|
6659
|
+
"competitor_signal",
|
|
6660
|
+
"market_trend",
|
|
6661
|
+
"market_segment",
|
|
6662
|
+
"classification_axis",
|
|
6663
|
+
"classification_value",
|
|
6664
|
+
// ── Product Specification (1): a configuration_axis is a structural
|
|
6665
|
+
// DECLARATION (a lever and its closed value set), not a thing that
|
|
6666
|
+
// progresses through states. Same reading as classification_axis above.
|
|
6667
|
+
"configuration_axis",
|
|
6668
|
+
// ── User Research (5 of 8): participants, quotes, observations,
|
|
6669
|
+
// clusters, survey responses are immutable data points ────────────────
|
|
6670
|
+
"participant",
|
|
6671
|
+
"observation",
|
|
6672
|
+
"quote",
|
|
6673
|
+
"affinity_cluster",
|
|
6674
|
+
"survey_response",
|
|
6675
|
+
// ── UX Design (3 of 8): journey structural atoms ──────────────────────────
|
|
6676
|
+
"journey_step",
|
|
6677
|
+
"journey_phase",
|
|
6678
|
+
"journey_action",
|
|
6679
|
+
"screen_state",
|
|
6680
|
+
// ── Design System (3 of 7): tokens and annotations are atomic values ────
|
|
6681
|
+
"design_token",
|
|
6682
|
+
"annotation",
|
|
6683
|
+
// ── Brand (4 of 6): colour/typography/imagery/voice are reference atoms ──
|
|
6684
|
+
"brand_colour",
|
|
6685
|
+
"brand_typography",
|
|
6686
|
+
"brand_imagery",
|
|
6687
|
+
// ── Product Specification (4 of 5): criteria, grouping, historical, +
|
|
6688
|
+
// user_story (the templated promise is a stable design artefact; the paired
|
|
6689
|
+
// task carries the lifecycle). Re-canon story_statement → user_story at
|
|
6690
|
+
// v0.7.0/UPG-571. ─
|
|
6691
|
+
"acceptance_criterion",
|
|
6692
|
+
"changelog",
|
|
6693
|
+
"roadmap_theme",
|
|
6694
|
+
"user_story",
|
|
6695
|
+
// ── Strategy: metric is a measurement definition; metric_quality_assessment
|
|
6696
|
+
// is a point-in-time snapshot; value_stream is a mapped flow;
|
|
6697
|
+
// constraint carries its own `constraint_status` enum (binding/advisory/
|
|
6698
|
+
// lifted) as a property; base-node `status` not used.
|
|
6699
|
+
// vision/mission/capability/outcome get lifecycles in Phase B or C ─────
|
|
6700
|
+
"metric",
|
|
6701
|
+
"metric_quality_assessment",
|
|
6702
|
+
"value_stream",
|
|
6703
|
+
"constraint",
|
|
6704
|
+
// ── Engineering (15 of 17): DDD modelling primitives + infra references.
|
|
6705
|
+
// root_cause / symptom / fix are investigation findings, not workflows
|
|
6706
|
+
// (investigation itself has a lifecycle) ────────────────────────────────
|
|
6707
|
+
"aggregate",
|
|
6708
|
+
"domain_entity",
|
|
6709
|
+
"value_object",
|
|
6710
|
+
"command",
|
|
6711
|
+
"read_model",
|
|
6712
|
+
"domain_event",
|
|
6713
|
+
"bounded_context",
|
|
6714
|
+
"api_endpoint",
|
|
6715
|
+
"queue_topic",
|
|
6716
|
+
"build_artifact",
|
|
6717
|
+
"code_repository",
|
|
6718
|
+
"library_dependency",
|
|
6719
|
+
"integration_pattern",
|
|
6720
|
+
"data_flow",
|
|
6721
|
+
"root_cause",
|
|
6722
|
+
"symptom",
|
|
6723
|
+
"fix",
|
|
6724
|
+
// ── Business Model (6 of 9): BMC cells are structural frames ────────────
|
|
6725
|
+
"cost_structure",
|
|
6726
|
+
"unit_economics",
|
|
6727
|
+
"key_resource",
|
|
6728
|
+
"key_activity",
|
|
6729
|
+
"customer_relationship",
|
|
6730
|
+
"distribution_channel",
|
|
6731
|
+
// ── Go-To-Market (8 of 13): positioning/ICP/territory/sales_motion are
|
|
6732
|
+
// configuration; objection/rebuttal/proof_point are catalog entries ───
|
|
6733
|
+
"ideal_customer_profile",
|
|
6734
|
+
"positioning",
|
|
6735
|
+
"sales_motion",
|
|
6736
|
+
"territory",
|
|
6737
|
+
"objection",
|
|
6738
|
+
"rebuttal",
|
|
6739
|
+
"proof_point",
|
|
6740
|
+
// ── Team & Organisation (7 of 11): org structural atoms ───────────────────
|
|
6741
|
+
"team",
|
|
6742
|
+
"stakeholder",
|
|
6743
|
+
"person",
|
|
6744
|
+
"department",
|
|
6745
|
+
"skill",
|
|
6746
|
+
"ceremony",
|
|
6747
|
+
// ── Data & Analytics (4 of 10): lineage and domain containers, plus
|
|
6748
|
+
// atomic definitions ────────────────────────────────────────────────────
|
|
6749
|
+
"data_lineage",
|
|
6750
|
+
"data_domain",
|
|
6751
|
+
"data_quality_rule",
|
|
6752
|
+
// ── AI (3 of 10): traces and cost trackers are append-only; model
|
|
6753
|
+
// comparison snapshots are immutable ───────────────────────────────────
|
|
6754
|
+
"ai_cost_tracker",
|
|
6755
|
+
"ai_trace",
|
|
6756
|
+
// ── Automation (2 of 6): approvals and artifacts are ledger entries ──────
|
|
6757
|
+
"approval_record",
|
|
6758
|
+
"workflow_artifact",
|
|
6759
|
+
// ── Growth (7 of 9): funnels and attribution are structural definitions;
|
|
6760
|
+
// cohorts/segments are computed snapshots ────────────────────────────
|
|
6761
|
+
"funnel",
|
|
6762
|
+
"funnel_step",
|
|
6763
|
+
"acquisition_channel",
|
|
6764
|
+
"cohort",
|
|
6765
|
+
"behavioral_segment",
|
|
6766
|
+
"growth_loop",
|
|
6767
|
+
"attribution_model",
|
|
6768
|
+
// ── Sales (5 of 6): pipeline structure + accounts/contacts + forecast
|
|
6769
|
+
// snapshots. Only `deal` has a lifecycle (Phase B SALES_DEAL template) ─
|
|
6770
|
+
"account",
|
|
6771
|
+
"contact",
|
|
6772
|
+
"pipeline_sales",
|
|
6773
|
+
"pipeline_stage",
|
|
6774
|
+
"forecast",
|
|
6775
|
+
// ── Customer Success (5 of 9): journey structure + derived values ────────
|
|
6776
|
+
"customer_journey_stage",
|
|
6777
|
+
"touchpoint",
|
|
6778
|
+
"churn_reason",
|
|
6779
|
+
"service_level_agreement",
|
|
6780
|
+
// ── DevOps (5 of 10): indicators and budgets are measurement references;
|
|
6781
|
+
// strategies and pipelines are configuration ──────────────────────────
|
|
6782
|
+
"service_level_indicator",
|
|
6783
|
+
"service_level_objective",
|
|
6784
|
+
"error_budget",
|
|
6785
|
+
"ci_pipeline",
|
|
6786
|
+
"release_strategy",
|
|
6787
|
+
// ── Security (1 of 5): data classification is a reference label ──────────
|
|
6788
|
+
"data_classification",
|
|
6789
|
+
// ── Accessibility (2 of 4): standards and annotations ─────────────────────
|
|
6790
|
+
"a11y_standard",
|
|
6791
|
+
"a11y_annotation",
|
|
6792
|
+
// ── Testing (3 of 5): regression tests are reference definitions; test
|
|
6793
|
+
// results and coverage reports are immutable records ───────────────────
|
|
6794
|
+
"regression_test",
|
|
6795
|
+
"test_coverage_report",
|
|
6796
|
+
"test_result",
|
|
6797
|
+
// ── Customer Feedback (2 of 3): themes and votes are atomic data points ──
|
|
6798
|
+
"feedback_theme",
|
|
6799
|
+
"feedback_vote",
|
|
6800
|
+
// ── Pricing (2 of 4): trial and paywall are configuration ─────────────────
|
|
6801
|
+
"trial_config",
|
|
6802
|
+
"paywall",
|
|
6803
|
+
// ── Content (2 of 5): calendars and themes are planning configuration ────
|
|
6804
|
+
"content_calendar",
|
|
6805
|
+
"content_theme",
|
|
6806
|
+
// ── Legal (2 of 3): legal_entity is an org; contract_clause is an atom
|
|
6807
|
+
// of a contract (which has its own lifecycle) ───────────────────────────
|
|
6808
|
+
"legal_entity",
|
|
6809
|
+
"contract_clause",
|
|
6810
|
+
// ── Portfolio (3 of 3): structural containers ─────────────────────────────
|
|
6811
|
+
"organization",
|
|
6812
|
+
"portfolio",
|
|
6813
|
+
"product_area",
|
|
6814
|
+
// ── Program Management (2 of 3): allocations and registers are tracking
|
|
6815
|
+
// containers ────────────────────────────────────────────────────────────
|
|
6816
|
+
"resource_allocation",
|
|
6817
|
+
"risk_register",
|
|
6818
|
+
// ── Localisation (4 of 5): translation keys, configs, and regional
|
|
6819
|
+
// adaptations are reference data ────────────────────────────────────────
|
|
6820
|
+
"translation_key",
|
|
6821
|
+
"locale_config",
|
|
6822
|
+
"cultural_adaptation",
|
|
6823
|
+
"regional_pricing",
|
|
6824
|
+
// ── Marketing (1 of 4): SEO keywords are tracked reference terms ─────────
|
|
6825
|
+
"seo_keyword",
|
|
6826
|
+
// ── Ecosystem (2 of 2): partner tiers and rev-share terms are config ─────
|
|
6827
|
+
"partner_tier",
|
|
6828
|
+
"partner_revenue_share",
|
|
6829
|
+
// ── Validation (2 of 4): evidence + learning are data points captured
|
|
6830
|
+
// from experiment_runs (which have lifecycles); hypothesis_evidence was
|
|
6831
|
+
// lifecycle-free here but is deprecated at v0.4.0 ──────────────────────────────────────
|
|
6832
|
+
"evidence",
|
|
6833
|
+
"learning",
|
|
6834
|
+
// ── Workspace (1 of 4): a capture is a fact about a moment, not a workflow.
|
|
6835
|
+
// It is taken, and from then on it either still matches its subject or it
|
|
6836
|
+
// does not — which is what `content_hash` answers, not a phase. ─────────
|
|
6837
|
+
"capture"
|
|
6838
|
+
]);
|
|
6839
|
+
function isLifecycleFreeType(entityType) {
|
|
6840
|
+
return UPG_LIFECYCLE_FREE_TYPES.has(entityType);
|
|
6841
|
+
}
|
|
6842
|
+
var UPG_LIFECYCLE_PLANNED_TYPES = /* @__PURE__ */ new Set([
|
|
6843
|
+
// Phase B cleared this set entirely. Every type that was triaged
|
|
6844
|
+
// into Phase B/C/D under now either has a lifecycle in
|
|
6845
|
+
// `UPG_LIFECYCLES` or is documented as lifecycle-free in
|
|
6846
|
+
// `UPG_LIFECYCLE_FREE_TYPES`.
|
|
6847
|
+
//
|
|
6848
|
+
// Reintroduce entries here when a new entity type is added to the spec
|
|
6849
|
+
// and decided to be lifecycle-bearing but its phases have not yet been
|
|
6850
|
+
// designed. The `10-lifecycle-coverage` audit will surface uncovered
|
|
6851
|
+
// active types until they're either implemented or moved to the
|
|
6852
|
+
// free-types set.
|
|
6853
|
+
]);
|
|
6854
|
+
function isLifecyclePlannedType(entityType) {
|
|
6855
|
+
return UPG_LIFECYCLE_PLANNED_TYPES.has(entityType);
|
|
6856
|
+
}
|
|
6857
|
+
function getLifecycleRenderShape(entityType) {
|
|
6858
|
+
const lifecycle = getLifecycleForType(entityType);
|
|
6859
|
+
if (!lifecycle) return null;
|
|
6860
|
+
const terminalSet = new Set(lifecycle.terminal_phases);
|
|
6861
|
+
const phaseIndex = new Map(lifecycle.phases.map((p, i) => [p.id, i]));
|
|
6862
|
+
const states = lifecycle.phases.map((phase) => ({
|
|
6863
|
+
id: phase.id,
|
|
6864
|
+
label: phase.label,
|
|
6865
|
+
description: phase.description,
|
|
6866
|
+
terminal: terminalSet.has(phase.id)
|
|
6867
|
+
}));
|
|
6868
|
+
const transitions = [];
|
|
6869
|
+
for (const phase of lifecycle.phases) {
|
|
6870
|
+
const fromTerminal = terminalSet.has(phase.id);
|
|
6871
|
+
for (const to of phase.transitions_to) {
|
|
6872
|
+
const toTerminal = terminalSet.has(to);
|
|
6873
|
+
let kind;
|
|
6874
|
+
if (fromTerminal && !toTerminal) {
|
|
6875
|
+
kind = "reopen";
|
|
6876
|
+
} else if (toTerminal && !fromTerminal) {
|
|
6877
|
+
kind = "terminal";
|
|
6878
|
+
} else {
|
|
6879
|
+
const fromIdx = phaseIndex.get(phase.id) ?? 0;
|
|
6880
|
+
const toIdx = phaseIndex.get(to) ?? 0;
|
|
6881
|
+
kind = toIdx < fromIdx ? "backward" : "forward";
|
|
6882
|
+
}
|
|
6883
|
+
transitions.push({ from: phase.id, to, kind });
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
return {
|
|
6887
|
+
entity_type: lifecycle.entity_type,
|
|
6888
|
+
...lifecycle.template_id !== void 0 && { template_id: lifecycle.template_id },
|
|
6889
|
+
states,
|
|
6890
|
+
transitions,
|
|
6891
|
+
initial_state: lifecycle.initial_phase
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
// src/catalog/legacy-product-stages.ts
|
|
6896
|
+
var LEGACY_PRODUCT_STAGES = Object.freeze({
|
|
6897
|
+
idea: "concept"
|
|
6898
|
+
});
|
|
6899
|
+
function migrateProductStage(stage) {
|
|
6900
|
+
if (stage === void 0 || stage === null) {
|
|
6901
|
+
return void 0;
|
|
6902
|
+
}
|
|
6903
|
+
if (typeof stage !== "string") {
|
|
6904
|
+
return stage;
|
|
6905
|
+
}
|
|
6906
|
+
const lower = stage.toLowerCase();
|
|
6907
|
+
const mapped = LEGACY_PRODUCT_STAGES[lower];
|
|
6908
|
+
return mapped ?? stage;
|
|
6909
|
+
}
|
|
6910
|
+
function isLegacyProductStage(stage) {
|
|
6911
|
+
return typeof stage === "string" && stage.toLowerCase() in LEGACY_PRODUCT_STAGES;
|
|
6912
|
+
}
|
|
6913
|
+
|
|
6914
|
+
// src/catalog/entity-descriptions.ts
|
|
6915
|
+
var UPG_ENTITY_DESCRIPTIONS = Object.freeze({
|
|
6916
|
+
// Strategy
|
|
6917
|
+
product: "The product being created, the root of the graph",
|
|
6918
|
+
outcome: "A desired business or user outcome",
|
|
6919
|
+
objective: "A strategic goal (OKR)",
|
|
6920
|
+
key_result: "A measurable result tied to an objective",
|
|
6921
|
+
metric: "A unified metric that measures progress, health, or behaviour across the product",
|
|
6922
|
+
metric_quality_assessment: "An assessment of whether a metric is well-defined, measurable, and aligned to a meaningful outcome.",
|
|
6923
|
+
vision: "A long-term aspirational statement of the future state",
|
|
6924
|
+
mission: "The purpose and reason the product exists",
|
|
6925
|
+
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.",
|
|
6926
|
+
initiative: "A large coordinated effort to achieve a strategic goal",
|
|
6927
|
+
capability: "An ability that enables value delivery",
|
|
6928
|
+
value_stream: "An end-to-end flow delivering value to the customer",
|
|
6929
|
+
strategic_pillar: "A foundational principle that guides decisions",
|
|
6930
|
+
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",
|
|
6931
|
+
assumption: "A belief taken as true that underpins a strategy",
|
|
6932
|
+
decision: "A recorded decision with context, rationale, and consequences",
|
|
6933
|
+
constraint: "A limit, requirement, or ceiling the product must respect, whether a self-imposed principle or an externally imposed boundary",
|
|
6934
|
+
// User
|
|
6935
|
+
persona: "An archetype representing a user segment",
|
|
6936
|
+
job: "Job To Be Done: what the user is trying to accomplish",
|
|
6937
|
+
need: "A user need, pain, desire, or constraint",
|
|
6938
|
+
desired_outcome: "What the user hopes to achieve",
|
|
6939
|
+
job_step: "A discrete step within a Job To Be Done",
|
|
6940
|
+
switching_cost: "A barrier preventing users from switching alternatives",
|
|
6941
|
+
// Discovery
|
|
6942
|
+
opportunity: "A validated gap worth solving",
|
|
6943
|
+
solution: "A proposed approach to address an opportunity",
|
|
6944
|
+
feasibility_study: "An assessment of technical or business viability",
|
|
6945
|
+
design_sprint: "A time-boxed sprint to prototype and validate ideas",
|
|
6946
|
+
// Validation
|
|
6947
|
+
hypothesis: "A testable belief about a solution",
|
|
6948
|
+
experiment: "A test designed to validate a hypothesis",
|
|
6949
|
+
experiment_plan: "An experiment plan describing the hypothesis, setup, success criteria, and methodology before a test runs.",
|
|
6950
|
+
experiment_run: "An execution instance of an experiment that records actual conditions, observations, and raw results.",
|
|
6951
|
+
learning: "An insight gained from an experiment",
|
|
6952
|
+
test_plan: "A structured plan for testing a hypothesis",
|
|
6953
|
+
evidence: "Data supporting or refuting a hypothesis",
|
|
6954
|
+
research_plan: "A plan for gathering evidence to validate a draft entity",
|
|
6955
|
+
// Market Intelligence
|
|
6956
|
+
competitor: "A competing product or company",
|
|
6957
|
+
competitor_feature: "A feature offered by a competitor",
|
|
6958
|
+
market_trend: "An emerging trend in the market",
|
|
6959
|
+
market_segment: "A distinct group of potential customers",
|
|
6960
|
+
competitive_analysis: "A structured analysis of the competitive landscape",
|
|
6961
|
+
classification_axis: "A single dimension in a competitive classification matrix that defines one axis of comparison across alternatives.",
|
|
6962
|
+
classification_value: "A position or value on a classification axis, representing where one option lands on that dimension.",
|
|
6963
|
+
competitor_signal: "An observed competitor move or datapoint (a pricing change, launch, or hire) that feeds competitive analysis",
|
|
6964
|
+
// User Research
|
|
6965
|
+
research_study: "A planned research activity",
|
|
6966
|
+
insight: "A synthesised finding from research",
|
|
6967
|
+
participant: "A person participating in research",
|
|
6968
|
+
observation: "A specific behaviour or statement observed",
|
|
6969
|
+
quote: "A direct quote from a research participant",
|
|
6970
|
+
affinity_cluster: "A group of related observations",
|
|
6971
|
+
research_question: "A question guiding a research study",
|
|
6972
|
+
interview_guide: "A structured guide for conducting interviews",
|
|
6973
|
+
survey_response: "A response to a survey instrument",
|
|
6974
|
+
// Experience Design
|
|
6975
|
+
user_journey: "An end-to-end map of a user experience",
|
|
6976
|
+
journey_step: "A single step in a user journey",
|
|
6977
|
+
journey_phase: "A named phase in a user journey that groups a sequence of actions the user performs to reach a milestone.",
|
|
6978
|
+
journey_action: "A single step a user takes within a journey phase, the atomic unit of observable user behaviour.",
|
|
6979
|
+
user_flow: "A navigation path through the product",
|
|
6980
|
+
screen: "A distinct screen or view in the product",
|
|
6981
|
+
screen_state: "A specific state of a screen (e.g., empty, loading, error)",
|
|
6982
|
+
surface: "A place inside a screen, the features that occupy it, and the rule that decides who wins it",
|
|
6983
|
+
design_question: "An open design problem to explore",
|
|
6984
|
+
design_concept: "A possible design direction or approach",
|
|
6985
|
+
prototype: "An interactive mockup for testing",
|
|
6986
|
+
wireframe: "A low-fidelity structural layout",
|
|
6987
|
+
annotation: "A note or markup on a design artifact",
|
|
6988
|
+
interaction_spec: "A specification for an interaction behaviour",
|
|
6989
|
+
// Design System
|
|
6990
|
+
design_component: "A reusable UI component",
|
|
6991
|
+
design_token: "A design token (colour, spacing, typography)",
|
|
6992
|
+
design_system: "The root design system entity",
|
|
6993
|
+
design_pattern: "A documented design pattern",
|
|
6994
|
+
design_guideline: "A design usage guideline",
|
|
6995
|
+
// Brand Identity
|
|
6996
|
+
brand_identity: "The root brand identity entity",
|
|
6997
|
+
brand_colour: "A brand colour definition",
|
|
6998
|
+
brand_typography: "Brand typography specifications",
|
|
6999
|
+
brand_voice: "Brand voice and tone guidelines",
|
|
7000
|
+
brand_logo: "A brand logo or mark",
|
|
7001
|
+
brand_imagery: "Brand imagery and photography guidelines",
|
|
7002
|
+
// Product Specification
|
|
7003
|
+
feature: "A product capability or feature",
|
|
7004
|
+
feature_area: "A grouping of related features",
|
|
7005
|
+
epic: "A large body of work that can be broken into stories",
|
|
7006
|
+
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.`,
|
|
7007
|
+
acceptance_criterion: "A condition that must be met for a story to be done",
|
|
7008
|
+
release: "A shipped version of the product",
|
|
7009
|
+
task: "A unit of work within a story or epic",
|
|
7010
|
+
bug: "A defect or unexpected behaviour",
|
|
7011
|
+
roadmap: "A strategic plan of features and milestones",
|
|
7012
|
+
roadmap_item: "An item on the product roadmap",
|
|
7013
|
+
theme: "A strategic grouping of related features",
|
|
7014
|
+
roadmap_theme: "A customer problem used as the organising unit of a roadmap",
|
|
7015
|
+
changelog: "A record of changes shipped in a release",
|
|
7016
|
+
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.",
|
|
7017
|
+
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.",
|
|
7018
|
+
// Engineering
|
|
7019
|
+
bounded_context: "A DDD bounded context defining a service boundary",
|
|
7020
|
+
service: "A deployable service or microservice",
|
|
7021
|
+
domain_event: "An event published when something happens in the domain",
|
|
7022
|
+
api_contract: "An API contract or specification",
|
|
7023
|
+
technical_debt_item: "A known piece of technical debt",
|
|
7024
|
+
feature_flag: "A feature toggle for controlled rollout",
|
|
7025
|
+
deployment: "A deployment event",
|
|
7026
|
+
aggregate: "A DDD aggregate root",
|
|
7027
|
+
domain_entity: "A DDD domain entity: a core object with a distinct identity and lifecycle inside a bounded context",
|
|
7028
|
+
value_object: "A DDD value object",
|
|
7029
|
+
command: "A DDD command (write operation)",
|
|
7030
|
+
read_model: "A DDD read model (query projection)",
|
|
7031
|
+
api_endpoint: "A specific API endpoint",
|
|
7032
|
+
database_schema: "A database schema definition",
|
|
7033
|
+
queue_topic: "A message queue topic",
|
|
7034
|
+
build_artifact: "A build output (binary, container image)",
|
|
7035
|
+
code_repository: "A source code repository",
|
|
7036
|
+
library_dependency: "A third-party library dependency",
|
|
7037
|
+
integration_pattern: "An integration pattern (saga, event sourcing, etc.)",
|
|
7038
|
+
external_api: "An external API consumed by the product",
|
|
7039
|
+
data_flow: "A data flow between systems",
|
|
7040
|
+
investigation: "An investigation into an issue or incident",
|
|
7041
|
+
root_cause: "An identified root cause of an issue",
|
|
7042
|
+
symptom: "A symptom of a problem",
|
|
7043
|
+
fix: "A fix applied to resolve an issue",
|
|
7044
|
+
// Growth
|
|
7045
|
+
funnel: "A conversion funnel tracking user progression",
|
|
7046
|
+
funnel_step: "A stage within a conversion funnel",
|
|
7047
|
+
acquisition_channel: "A channel through which users are acquired",
|
|
7048
|
+
growth_campaign: "A growth-focused campaign",
|
|
7049
|
+
cohort: "A group of users sharing a common characteristic",
|
|
7050
|
+
behavioral_segment: "A user segment based on behaviour",
|
|
7051
|
+
growth_loop: "A self-reinforcing growth cycle",
|
|
7052
|
+
variant: "A variant in an A/B test",
|
|
7053
|
+
attribution_model: "A model for attributing conversions to touchpoints",
|
|
7054
|
+
// Business Model
|
|
7055
|
+
business_model: "The business model canvas or definition",
|
|
7056
|
+
value_proposition: "A unique value offered to customers",
|
|
7057
|
+
revenue_stream: "A source of revenue",
|
|
7058
|
+
pricing_tier: "A pricing tier or plan",
|
|
7059
|
+
cost_structure: "A cost category or structure",
|
|
7060
|
+
unit_economics: "Per-unit economic metrics (CAC, LTV, etc.)",
|
|
7061
|
+
partnership: "A strategic partnership",
|
|
7062
|
+
key_resource: "A key resource required by the business",
|
|
7063
|
+
key_activity: "A key activity the business performs",
|
|
7064
|
+
target_customer_segment: "A target customer segment",
|
|
7065
|
+
customer_relationship: "A type of customer relationship",
|
|
7066
|
+
distribution_channel: "A channel for delivering value to customers",
|
|
7067
|
+
// Go-To-Market
|
|
7068
|
+
gtm_strategy: "A go-to-market strategy",
|
|
7069
|
+
ideal_customer_profile: "The ideal customer profile (ICP)",
|
|
7070
|
+
positioning: "Product positioning statement",
|
|
7071
|
+
messaging: "Messaging framework and key messages",
|
|
7072
|
+
launch: "A product launch event",
|
|
7073
|
+
content_strategy: "A content strategy for thought leadership",
|
|
7074
|
+
sales_motion: "A repeatable sales motion",
|
|
7075
|
+
competitive_battle_card: "A competitive battle card for sales enablement",
|
|
7076
|
+
demand_gen_program: "A demand generation program",
|
|
7077
|
+
territory: "A sales territory",
|
|
7078
|
+
objection: "A common sales objection",
|
|
7079
|
+
rebuttal: "A rebuttal to a sales objection",
|
|
7080
|
+
proof_point: "Evidence supporting a sales claim",
|
|
7081
|
+
// Team & Organisation
|
|
7082
|
+
team: "A cross-functional team",
|
|
7083
|
+
role: "A role within a team",
|
|
7084
|
+
stakeholder: "A person with influence over the product",
|
|
7085
|
+
team_okr: "A team-level OKR",
|
|
7086
|
+
retrospective: "A team retrospective",
|
|
7087
|
+
dependency: "A cross-team or system dependency",
|
|
7088
|
+
department: "An organisational department",
|
|
7089
|
+
skill: "A skill or competency within a team",
|
|
7090
|
+
ceremony: "A recurring team ritual (standup, planning)",
|
|
7091
|
+
capacity_plan: "A plan for allocating team capacity",
|
|
7092
|
+
person: "A named individual (owner, stakeholder, or participant) referenced across the graph",
|
|
7093
|
+
// Data & Analytics
|
|
7094
|
+
data_source: "A data source or integration",
|
|
7095
|
+
event_schema: "An event schema for tracking",
|
|
7096
|
+
dashboard: "An analytics dashboard",
|
|
7097
|
+
data_model: "A data model or schema",
|
|
7098
|
+
data_quality_rule: "A data quality validation rule",
|
|
7099
|
+
data_product: "A curated, reusable data asset",
|
|
7100
|
+
data_pipeline: "An automated pipeline for data transformation",
|
|
7101
|
+
data_lineage: "A record of data origin and transformations",
|
|
7102
|
+
glossary_term: "A defined term for shared understanding",
|
|
7103
|
+
data_domain: "A logical grouping of related data assets",
|
|
7104
|
+
report: "A structured analytical report",
|
|
7105
|
+
// Customer Success
|
|
7106
|
+
support_ticket: "Customer support request or issue",
|
|
7107
|
+
customer_feedback: "Voice-of-customer input",
|
|
7108
|
+
churn_reason: "Why a customer left",
|
|
7109
|
+
customer_health_score: "A composite health score for a customer",
|
|
7110
|
+
playbook: "A standard operating procedure for CS",
|
|
7111
|
+
service_level_agreement: "A service level agreement with customers",
|
|
7112
|
+
customer_journey_stage: "A stage in the post-sale customer journey",
|
|
7113
|
+
touchpoint: "A customer interaction touchpoint",
|
|
7114
|
+
success_milestone: "A customer success milestone",
|
|
7115
|
+
service_blueprint: "A blueprint of the full service delivery",
|
|
7116
|
+
// Content & Knowledge
|
|
7117
|
+
content_piece: "A piece of content (article, video, etc.)",
|
|
7118
|
+
knowledge_base_article: "A knowledge base article",
|
|
7119
|
+
brand_asset: "A brand asset (logo, image, etc.)",
|
|
7120
|
+
internal_doc: "An internal documentation page",
|
|
7121
|
+
prompt_template: "A reusable prompt template",
|
|
7122
|
+
content_calendar: "A content publication calendar",
|
|
7123
|
+
content_theme: "A thematic grouping for content",
|
|
7124
|
+
documentation_template: "A template for documentation",
|
|
7125
|
+
document: "A general-purpose document",
|
|
7126
|
+
// Legal
|
|
7127
|
+
legal_entity: "A legal entity (company, subsidiary)",
|
|
7128
|
+
ip_asset: "An intellectual property asset",
|
|
7129
|
+
contract: "A legal contract",
|
|
7130
|
+
contract_clause: "A clause within a contract",
|
|
7131
|
+
privacy_policy: "A privacy policy",
|
|
7132
|
+
// Compliance
|
|
7133
|
+
compliance_requirement: "A compliance requirement",
|
|
7134
|
+
risk: "A risk to the product or business",
|
|
7135
|
+
data_contract: "A data-sharing contract",
|
|
7136
|
+
audit_log_policy: "An audit logging policy",
|
|
7137
|
+
compliance_framework: "A compliance framework (SOC 2, GDPR, etc.)",
|
|
7138
|
+
security_audit: "A security audit",
|
|
7139
|
+
// DevOps & Platform
|
|
7140
|
+
service_level_indicator: "A service level indicator (SLI)",
|
|
7141
|
+
service_level_objective: "A service level objective (SLO)",
|
|
7142
|
+
error_budget: "An error budget for a service",
|
|
7143
|
+
incident: "A production incident",
|
|
7144
|
+
postmortem: "A post-incident review",
|
|
7145
|
+
runbook: "A runbook for incident response",
|
|
7146
|
+
monitor: "A monitoring check",
|
|
7147
|
+
alert_rule: "An alerting rule",
|
|
7148
|
+
ci_pipeline: "A CI/CD pipeline",
|
|
7149
|
+
release_strategy: "A release strategy (canary, blue-green, etc.)",
|
|
7150
|
+
on_call_rotation: "An on-call rotation schedule",
|
|
7151
|
+
infrastructure_component: "An infrastructure component (server, CDN, etc.)",
|
|
7152
|
+
// Security
|
|
7153
|
+
threat_model: "A threat model for the system",
|
|
7154
|
+
threat: "A specific security threat",
|
|
7155
|
+
vulnerability: "A known vulnerability",
|
|
7156
|
+
security_control: "A security control or mitigation",
|
|
7157
|
+
security_policy: "A security policy",
|
|
7158
|
+
penetration_test: "A penetration test",
|
|
7159
|
+
security_review: "A security review",
|
|
7160
|
+
data_classification: "A data classification level",
|
|
7161
|
+
access_policy: "An access control policy",
|
|
7162
|
+
// Accessibility
|
|
7163
|
+
a11y_standard: "An accessibility standard (WCAG, Section 508)",
|
|
7164
|
+
a11y_guideline: "An accessibility guideline",
|
|
7165
|
+
a11y_audit: "An accessibility audit",
|
|
7166
|
+
a11y_issue: "An accessibility issue",
|
|
7167
|
+
a11y_annotation: "An accessibility annotation on a design",
|
|
7168
|
+
// Quality Assurance
|
|
7169
|
+
test_suite: "A suite of related tests",
|
|
7170
|
+
test_case: "An individual test case",
|
|
7171
|
+
qa_session: "An exploratory QA session",
|
|
7172
|
+
regression_test: "A regression test",
|
|
7173
|
+
test_coverage_report: "A test coverage report",
|
|
7174
|
+
test_environment: "A testing environment",
|
|
7175
|
+
test_result: "A test execution result",
|
|
7176
|
+
// Customer Feedback
|
|
7177
|
+
feedback_program: "A structured feedback collection program",
|
|
7178
|
+
feature_request: "A user-submitted feature request",
|
|
7179
|
+
feedback_vote: "A vote on a feature request or feedback item",
|
|
7180
|
+
nps_campaign: "A Net Promoter Score survey campaign",
|
|
7181
|
+
user_advisory_board: "A panel of users providing ongoing feedback",
|
|
7182
|
+
beta_program: "A beta testing program",
|
|
7183
|
+
feedback_theme: "A recurring theme across feedback items",
|
|
7184
|
+
// Pricing & Packaging
|
|
7185
|
+
pricing_strategy: "An overarching pricing strategy",
|
|
7186
|
+
package: "A bundled package of features at a price point",
|
|
7187
|
+
discount_strategy: "A strategy for offering discounts",
|
|
7188
|
+
trial_config: "Configuration for a free trial",
|
|
7189
|
+
paywall: "A paywall gating premium features",
|
|
7190
|
+
// AI & Machine Learning
|
|
7191
|
+
ai_model: "An AI or ML model used within the product",
|
|
7192
|
+
prompt_version: "A version of a prompt template",
|
|
7193
|
+
eval_benchmark: "A benchmark for evaluating AI quality",
|
|
7194
|
+
eval_run: "An evaluation run against a benchmark",
|
|
7195
|
+
ai_cost_tracker: "A tracker for AI inference costs",
|
|
7196
|
+
hallucination_report: "A report of an AI hallucination",
|
|
7197
|
+
ai_guardrail: "A guardrail for AI safety",
|
|
7198
|
+
model_comparison: "A comparison between AI models",
|
|
7199
|
+
ai_experiment: "An AI-focused experiment",
|
|
7200
|
+
ai_dataset: "A dataset for AI training or evaluation",
|
|
7201
|
+
ai_trace: "An AI inference trace",
|
|
7202
|
+
// Workflows & Agents
|
|
7203
|
+
workflow_template: "A reusable workflow template",
|
|
7204
|
+
workflow_run: "An execution of a workflow",
|
|
7205
|
+
agent_definition: "An autonomous agent definition",
|
|
7206
|
+
agent_session: "An agent working session",
|
|
7207
|
+
review_gate: "A human review checkpoint in a workflow",
|
|
7208
|
+
approval_record: "A record of an approval decision",
|
|
7209
|
+
agent_skill: "A capability of an agent",
|
|
7210
|
+
agent_hook: "A trigger connecting an agent to an event",
|
|
7211
|
+
workflow_artifact: "An output artifact from a workflow run",
|
|
7212
|
+
agent_task: "A discrete unit of agent work",
|
|
7213
|
+
// Portfolio
|
|
7214
|
+
organization: "The top-level organisational entity",
|
|
7215
|
+
portfolio: "A grouping of products by strategic axis",
|
|
7216
|
+
product_area: "A grouping of products by organisational axis",
|
|
7217
|
+
// Sales & Revenue
|
|
7218
|
+
account: "A customer account",
|
|
7219
|
+
contact: "A person within an account",
|
|
7220
|
+
lead: "An inbound lead",
|
|
7221
|
+
deal: "An active sales opportunity",
|
|
7222
|
+
pipeline_sales: "A sales pipeline",
|
|
7223
|
+
pipeline_stage: "A stage in the sales pipeline",
|
|
7224
|
+
quote_document: "A formal quote document",
|
|
7225
|
+
subscription: "A recurring subscription",
|
|
7226
|
+
invoice: "An invoice for billing",
|
|
7227
|
+
forecast: "A revenue forecast",
|
|
7228
|
+
// Program Management
|
|
7229
|
+
program: "A program coordinating multiple projects",
|
|
7230
|
+
project: "A project within a program",
|
|
7231
|
+
milestone: "A key date or achievement",
|
|
7232
|
+
risk_register: "A register of project risks",
|
|
7233
|
+
change_request: "A request to change project scope",
|
|
7234
|
+
deliverable: "A deliverable from a project",
|
|
7235
|
+
resource_allocation: "An allocation of team capacity",
|
|
7236
|
+
status_report: "A project status report",
|
|
7237
|
+
// Marketing
|
|
7238
|
+
marketing_strategy: "A marketing strategy",
|
|
7239
|
+
marketing_channel: "A marketing channel",
|
|
7240
|
+
marketing_campaign_plan: "A marketing campaign plan",
|
|
7241
|
+
email_sequence: "An email nurture sequence",
|
|
7242
|
+
social_post: "A social media post",
|
|
7243
|
+
seo_keyword: "An SEO keyword target",
|
|
7244
|
+
ad_creative: "An ad creative",
|
|
7245
|
+
press_release: "A press release",
|
|
7246
|
+
event: "A marketing event",
|
|
7247
|
+
community_initiative: "A community engagement initiative",
|
|
7248
|
+
// Localisation
|
|
7249
|
+
locale: "A supported locale (language + region)",
|
|
7250
|
+
translation_key: "A translatable string key",
|
|
7251
|
+
translation_bundle: "A bundle of translation keys",
|
|
7252
|
+
locale_config: "Per-locale configuration settings",
|
|
7253
|
+
cultural_adaptation: "A region-specific cultural adaptation",
|
|
7254
|
+
regional_pricing: "Location-based pricing",
|
|
7255
|
+
// Customer Education
|
|
7256
|
+
education_program: "A customer education program",
|
|
7257
|
+
tutorial: "A step-by-step tutorial",
|
|
7258
|
+
walkthrough: "A guided product walkthrough",
|
|
7259
|
+
webinar: "A live or recorded webinar",
|
|
7260
|
+
certification: "A certification program",
|
|
7261
|
+
help_video: "A help video",
|
|
7262
|
+
learning_path: "A structured learning path",
|
|
7263
|
+
// Partners & Ecosystem
|
|
7264
|
+
partner_program: "A partner program",
|
|
7265
|
+
partner_tier: "A partner tier level",
|
|
7266
|
+
api_ecosystem: "An API ecosystem",
|
|
7267
|
+
marketplace_listing: "A marketplace listing",
|
|
7268
|
+
developer_portal: "A developer portal",
|
|
7269
|
+
integration_partner: "An integration partner",
|
|
7270
|
+
partner_revenue_share: "A partner revenue sharing arrangement",
|
|
7271
|
+
// Foundations
|
|
7272
|
+
operating_lifecycle: "A canonical, ordered (often cyclic) operating process a product's journey phases map onto",
|
|
7273
|
+
operating_stage: "One ordered stage of an operating_lifecycle",
|
|
7274
|
+
specification: "A canonical open standard or specification that primitives and products conform to",
|
|
7275
|
+
primitive: "A canonical reusable building block defined by a specification",
|
|
7276
|
+
// Workspace
|
|
7277
|
+
workspace: "A spatial thinking space for arranging entities",
|
|
7278
|
+
framework_exercise: "A recorded run of a product framework (RICE, OST, and the like) over specific entities, holding its inputs and outputs",
|
|
7279
|
+
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.",
|
|
7280
|
+
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."
|
|
7281
|
+
});
|
|
7282
|
+
function getEntityDescription(type) {
|
|
7283
|
+
return UPG_ENTITY_DESCRIPTIONS[type];
|
|
7284
|
+
}
|
|
7285
|
+
|
|
7286
|
+
// src/shapes/base-node.ts
|
|
7287
|
+
var BASE_NODE_FIELD_PRESENCE = {
|
|
7288
|
+
id: true,
|
|
7289
|
+
type: true,
|
|
7290
|
+
title: true,
|
|
7291
|
+
slug: true,
|
|
7292
|
+
aliases: true,
|
|
7293
|
+
key: true,
|
|
7294
|
+
description: true,
|
|
7295
|
+
tags: true,
|
|
7296
|
+
status: true,
|
|
7297
|
+
archived: true,
|
|
7298
|
+
archived_at: true,
|
|
7299
|
+
source_id: true,
|
|
7300
|
+
source_type: true,
|
|
7301
|
+
mapping_confidence: true,
|
|
7302
|
+
external_tool: true,
|
|
7303
|
+
external_ref: true,
|
|
7304
|
+
external_id: true,
|
|
7305
|
+
external_links: true,
|
|
7306
|
+
created_at: true,
|
|
7307
|
+
updated_at: true,
|
|
7308
|
+
properties: true
|
|
7309
|
+
};
|
|
7310
|
+
var UPG_BASE_NODE_FIELDS = Object.freeze(
|
|
7311
|
+
Object.keys(BASE_NODE_FIELD_PRESENCE)
|
|
7312
|
+
);
|
|
7313
|
+
var UPG_BASE_NODE_FIELD_SET = new Set(UPG_BASE_NODE_FIELDS);
|
|
7314
|
+
var UPG_BASE_NODE_SPECIAL_MERGE_FIELDS = /* @__PURE__ */ new Set([
|
|
7315
|
+
"id",
|
|
7316
|
+
"type",
|
|
7317
|
+
"slug",
|
|
7318
|
+
"aliases",
|
|
7319
|
+
"properties"
|
|
7320
|
+
]);
|
|
7321
|
+
|
|
7322
|
+
// src/shapes/document.ts
|
|
7323
|
+
var UPG_CROSS_ONLY_EDGE_TYPES = [
|
|
7324
|
+
// Peer overlap: "these two products share / overlap on this thing." Symmetric,
|
|
7325
|
+
// no canonical instance_of identity. shares_job / shares_need are the job/need
|
|
7326
|
+
// siblings of the persona/competitor/metric peers (connective layer, 0.13.1).
|
|
7327
|
+
"shares_persona",
|
|
7328
|
+
"shares_competitor",
|
|
7329
|
+
"shares_metric",
|
|
7330
|
+
"shares_job",
|
|
7331
|
+
"shares_need",
|
|
7332
|
+
// Product-to-product relationships.
|
|
7333
|
+
"depends_on_product",
|
|
7334
|
+
// runtime dependency (built-on / dogfood), not containment
|
|
7335
|
+
"cannibalises",
|
|
7336
|
+
"succeeds",
|
|
7337
|
+
"hosts",
|
|
7338
|
+
// host runs the hosted product inside itself (container -> contained)
|
|
7339
|
+
// OKR / measurement rollup primitives: the cascade ACROSS products (subordinate
|
|
7340
|
+
// -> superior). `contributes_to` rolls a product strategy entity up to a higher-
|
|
7341
|
+
// level one; `rolls_up_to` feeds a product metric into a higher-level north-star.
|
|
7342
|
+
"contributes_to",
|
|
7343
|
+
"rolls_up_to",
|
|
7344
|
+
// Canonical instance + area-to-audience: the target lives in the portfolio
|
|
7345
|
+
// `registry` section (`registry/{node_id}`). `instance_of` is same-type
|
|
7346
|
+
// canonical-to-instance; the area edges are minted via `link_area_to_audience`.
|
|
7347
|
+
"instance_of",
|
|
7348
|
+
"area_serves_persona",
|
|
7349
|
+
"area_targets_market_segment",
|
|
7350
|
+
// Foundations (0.9.12): a product-graph entity links to a canonical specification
|
|
7351
|
+
// or primitive. Registry-internal spec-to-spec links are catalog edges, not these.
|
|
7352
|
+
//
|
|
7353
|
+
// CORRECTED 0.34.0 — the target contract. This comment said `registry/{node_id}`,
|
|
7354
|
+
// "same shape as instance_of". Every live instance disagrees: all three in the
|
|
7355
|
+
// field target `{product_id}/{node_id}`, and the portfolio's `registry` section
|
|
7356
|
+
// is EMPTY. A reader following the old sentence would look for a registry entry
|
|
7357
|
+
// that has never existed. Both target forms are legitimate — a specification held
|
|
7358
|
+
// as a canonical in the registry, or one held inside the product graph that owns
|
|
7359
|
+
// it — and the field has so far only produced the second.
|
|
7360
|
+
//
|
|
7361
|
+
// THE GAP THIS TIER LEAVES, restated 0.34.0 because the 0.33.0 wording named a
|
|
7362
|
+
// case no graph instantiates. It is NOT "a specification in a graph with no
|
|
7363
|
+
// portfolio around it". It is the HOLDER PRODUCT, and it is live today: the
|
|
7364
|
+
// product graph that HOLDS a specification node has three sibling products
|
|
7365
|
+
// reaching it through this tier and no way to state its own relation to it,
|
|
7366
|
+
// because the cross tier requires two distinct products and there is no
|
|
7367
|
+
// within-graph conformance edge. Not closed in 0.34.0. The verb the live case
|
|
7368
|
+
// wants is `defines` rather than `conforms_to` — a product does not conform to
|
|
7369
|
+
// the specification it authors — and one instance of evidence is not enough to
|
|
7370
|
+
// mint a catalog edge in the area that cost 0.33.0 its shape mid-build. The
|
|
7371
|
+
// condition is the second holder-product instance, or any single-product graph
|
|
7372
|
+
// modelling a specification it conforms to.
|
|
7373
|
+
"product_implements_specification",
|
|
7374
|
+
"product_exposes_specification",
|
|
7375
|
+
"feature_conforms_to_specification",
|
|
7376
|
+
"api_contract_speaks_specification",
|
|
7377
|
+
"product_exposes_primitive",
|
|
7378
|
+
"feature_manipulates_primitive",
|
|
7379
|
+
"primitive_stored_as_data_type"
|
|
7380
|
+
];
|
|
7381
|
+
var UPG_CROSS_EDGE_TYPES = [
|
|
7382
|
+
...UPG_CROSS_ONLY_EDGE_TYPES,
|
|
7383
|
+
...UPG_CROSS_ELIGIBLE_CATALOG_EDGE_TYPES
|
|
7384
|
+
];
|
|
7385
|
+
var REGISTRY_PRODUCT_ID = "registry";
|
|
7386
|
+
var UPG_PORTFOLIO_KINDS = ["owned", "watched", "strategic", "internal", "gtm"];
|
|
7387
|
+
|
|
7388
|
+
// src/registry/entity-type-resolution.ts
|
|
7389
|
+
var TYPES = getTypes();
|
|
7390
|
+
var TYPES_SET = new Set(TYPES);
|
|
7391
|
+
var UnknownEntityTypeError = class extends Error {
|
|
7392
|
+
suggestions;
|
|
7393
|
+
rawType;
|
|
7394
|
+
constructor(rawType, suggestions) {
|
|
7395
|
+
const suffix = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : "";
|
|
7396
|
+
super(`Unknown entity type: "${rawType}".${suffix}`);
|
|
7397
|
+
this.name = "UnknownEntityTypeError";
|
|
7398
|
+
this.rawType = rawType;
|
|
7399
|
+
this.suggestions = suggestions;
|
|
7400
|
+
}
|
|
7401
|
+
};
|
|
7402
|
+
function withinEditDistance1(a, b) {
|
|
7403
|
+
if (a === b) return true;
|
|
7404
|
+
const al = a.length;
|
|
7405
|
+
const bl = b.length;
|
|
7406
|
+
if (Math.abs(al - bl) > 1) return false;
|
|
7407
|
+
let i = 0;
|
|
7408
|
+
let j = 0;
|
|
7409
|
+
let edits = 0;
|
|
7410
|
+
while (i < al && j < bl) {
|
|
7411
|
+
if (a[i] !== b[j]) {
|
|
7412
|
+
if (++edits > 1) return false;
|
|
7413
|
+
if (al > bl) i++;
|
|
7414
|
+
else if (bl > al) j++;
|
|
7415
|
+
else {
|
|
7416
|
+
i++;
|
|
7417
|
+
j++;
|
|
7418
|
+
}
|
|
7419
|
+
} else {
|
|
7420
|
+
i++;
|
|
7421
|
+
j++;
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
if (i < al || j < bl) edits++;
|
|
7425
|
+
return edits <= 1;
|
|
7426
|
+
}
|
|
7427
|
+
function resolveEntityType(rawType) {
|
|
7428
|
+
if (typeof rawType !== "string" || rawType.length === 0) {
|
|
7429
|
+
throw new UnknownEntityTypeError(String(rawType ?? ""), []);
|
|
7430
|
+
}
|
|
7431
|
+
if (TYPES_SET.has(rawType)) {
|
|
7432
|
+
return { canonical: rawType };
|
|
7433
|
+
}
|
|
7434
|
+
const replacement = getReplacementType(rawType);
|
|
7435
|
+
if (replacement && TYPES_SET.has(replacement)) {
|
|
7436
|
+
return { canonical: replacement, alias: { from: rawType, to: replacement } };
|
|
7437
|
+
}
|
|
7438
|
+
const suggestions = [];
|
|
7439
|
+
for (const t of TYPES) {
|
|
7440
|
+
if (withinEditDistance1(t, rawType)) suggestions.push(t);
|
|
7441
|
+
if (suggestions.length >= 5) break;
|
|
7442
|
+
}
|
|
7443
|
+
throw new UnknownEntityTypeError(rawType, suggestions);
|
|
7444
|
+
}
|
|
7445
|
+
|
|
7446
|
+
// src/grammar/hierarchy.ts
|
|
7447
|
+
var UPG_VALID_CHILDREN = {
|
|
7448
|
+
// ── Foundations (0.9.12): registry canonicals that self-nest. A
|
|
7449
|
+
// specification extends a specification (a dialect of a parent spec); a
|
|
7450
|
+
// primitive composes primitives (a block contains spans). These back the
|
|
7451
|
+
// hierarchy-classified specification_extends_specification /
|
|
7452
|
+
// primitive_composes_primitive catalog edges.
|
|
7453
|
+
specification: ["specification"],
|
|
7454
|
+
primitive: ["primitive"],
|
|
7455
|
+
// An operating_lifecycle (e.g. the content-ops lifecycle) contains its ordered
|
|
7456
|
+
// operating_stage children. Backs the operating_lifecycle_contains_operating_stage edge.
|
|
7457
|
+
operating_lifecycle: ["operating_stage"],
|
|
7458
|
+
// ── Product (root) ─────────────────────────────────────────────────────────
|
|
7459
|
+
product: [
|
|
7460
|
+
// Strategy
|
|
7461
|
+
"vision",
|
|
7462
|
+
"outcome",
|
|
7463
|
+
"objective",
|
|
7464
|
+
"metric",
|
|
7465
|
+
"decision",
|
|
7466
|
+
"constraint",
|
|
7467
|
+
// User
|
|
7468
|
+
"persona",
|
|
7469
|
+
// Discovery (via outcome children)
|
|
7470
|
+
// Validation (via hypothesis children)
|
|
7471
|
+
// Market Intelligence
|
|
7472
|
+
"competitor",
|
|
7473
|
+
"market_trend",
|
|
7474
|
+
"market_segment",
|
|
7475
|
+
"competitive_analysis",
|
|
7476
|
+
// User Research
|
|
7477
|
+
"research_study",
|
|
7478
|
+
// Experience Design
|
|
7479
|
+
"user_journey",
|
|
7480
|
+
"user_flow",
|
|
7481
|
+
"wireframe",
|
|
7482
|
+
"screen",
|
|
7483
|
+
// Design System
|
|
7484
|
+
"design_system",
|
|
7485
|
+
"design_component",
|
|
7486
|
+
// Brand Identity
|
|
7487
|
+
"brand_identity",
|
|
7488
|
+
// Product Specification
|
|
7489
|
+
"feature",
|
|
7490
|
+
"feature_area",
|
|
7491
|
+
"release",
|
|
7492
|
+
"roadmap",
|
|
7493
|
+
"roadmap_theme",
|
|
7494
|
+
"planning_cycle",
|
|
7495
|
+
// Configuration (0.30.0): the levers whose values select which surface tree
|
|
7496
|
+
// the product renders. Few per product, and top-level because an axis is a
|
|
7497
|
+
// product-wide fact rather than a child of any one feature or surface.
|
|
7498
|
+
"configuration_axis",
|
|
7499
|
+
// Classification (0.32.0): the product's own taxonomies — the grouped-label
|
|
7500
|
+
// case, one named group over a set of values. Same reasoning as the
|
|
7501
|
+
// configuration axis above, and until now the only parent a
|
|
7502
|
+
// classification_axis could have was a competitive_analysis.
|
|
7503
|
+
"classification_axis",
|
|
7504
|
+
// Engineering
|
|
7505
|
+
"bounded_context",
|
|
7506
|
+
"code_repository",
|
|
7507
|
+
"integration_pattern",
|
|
7508
|
+
"external_api",
|
|
7509
|
+
"data_flow",
|
|
7510
|
+
// Growth
|
|
7511
|
+
"funnel",
|
|
7512
|
+
"acquisition_channel",
|
|
7513
|
+
"cohort",
|
|
7514
|
+
"behavioral_segment",
|
|
7515
|
+
"growth_loop",
|
|
7516
|
+
"attribution_model",
|
|
7517
|
+
// Business Model
|
|
7518
|
+
"business_model",
|
|
7519
|
+
// Go-To-Market
|
|
7520
|
+
"gtm_strategy",
|
|
7521
|
+
// Team & Organisation
|
|
7522
|
+
// `person` is containment-free (see UPG_CONTAINMENT_FREE_TYPES below)
|
|
7523
|
+
// (referenced via edges, not nested under product)
|
|
7524
|
+
"team",
|
|
7525
|
+
"stakeholder",
|
|
7526
|
+
"department",
|
|
7527
|
+
// Data & Analytics
|
|
7528
|
+
"data_source",
|
|
7529
|
+
"event_schema",
|
|
7530
|
+
"dashboard",
|
|
7531
|
+
"data_domain",
|
|
7532
|
+
"glossary_term",
|
|
7533
|
+
// Content & Knowledge
|
|
7534
|
+
"content_piece",
|
|
7535
|
+
"knowledge_base_article",
|
|
7536
|
+
"brand_asset",
|
|
7537
|
+
"document",
|
|
7538
|
+
"documentation_template",
|
|
7539
|
+
// Legal
|
|
7540
|
+
"legal_entity",
|
|
7541
|
+
"privacy_policy",
|
|
7542
|
+
// Compliance
|
|
7543
|
+
"compliance_requirement",
|
|
7544
|
+
"risk",
|
|
7545
|
+
"data_contract",
|
|
7546
|
+
"audit_log_policy",
|
|
7547
|
+
"compliance_framework",
|
|
7548
|
+
// DevOps & Platform
|
|
7549
|
+
"service_level_objective",
|
|
7550
|
+
"incident",
|
|
7551
|
+
"runbook",
|
|
7552
|
+
"monitor",
|
|
7553
|
+
"ci_pipeline",
|
|
7554
|
+
"release_strategy",
|
|
7555
|
+
"on_call_rotation",
|
|
7556
|
+
"infrastructure_component",
|
|
7557
|
+
// Security
|
|
7558
|
+
"threat_model",
|
|
7559
|
+
"security_control",
|
|
7560
|
+
"security_policy",
|
|
7561
|
+
"penetration_test",
|
|
7562
|
+
"security_review",
|
|
7563
|
+
"data_classification",
|
|
7564
|
+
"access_policy",
|
|
7565
|
+
// Accessibility
|
|
7566
|
+
"a11y_standard",
|
|
7567
|
+
"a11y_audit",
|
|
7568
|
+
"a11y_annotation",
|
|
7569
|
+
// Quality Assurance
|
|
7570
|
+
"test_plan",
|
|
7571
|
+
"test_suite",
|
|
7572
|
+
"qa_session",
|
|
7573
|
+
"test_coverage_report",
|
|
7574
|
+
"test_environment",
|
|
7575
|
+
// Customer Feedback
|
|
7576
|
+
"feedback_program",
|
|
7577
|
+
"user_advisory_board",
|
|
7578
|
+
"beta_program",
|
|
7579
|
+
// Pricing & Packaging
|
|
7580
|
+
"pricing_strategy",
|
|
7581
|
+
// AI & Machine Learning
|
|
7582
|
+
"ai_model",
|
|
7583
|
+
"model_comparison",
|
|
7584
|
+
// Workflows & Agents
|
|
7585
|
+
"workflow_template",
|
|
7586
|
+
"agent_definition",
|
|
7587
|
+
// Sales & Revenue
|
|
7588
|
+
"pipeline_sales",
|
|
7589
|
+
"account",
|
|
7590
|
+
"lead",
|
|
7591
|
+
"subscription",
|
|
7592
|
+
"forecast",
|
|
7593
|
+
// Program Management
|
|
7594
|
+
// `milestone` is a SECOND, shallower parent here (Portfolio Phase 2,
|
|
7595
|
+
// 2026-08-14), alongside its existing `project` parent: a product can own a
|
|
7596
|
+
// milestone outright with no program/project above it. Required by the
|
|
7597
|
+
// hierarchy-orphan guard now that `product_targets_milestone` exists.
|
|
7598
|
+
"program",
|
|
7599
|
+
"milestone",
|
|
7600
|
+
// Marketing
|
|
7601
|
+
"marketing_strategy",
|
|
7602
|
+
"press_release",
|
|
7603
|
+
"event",
|
|
7604
|
+
"community_initiative",
|
|
7605
|
+
// Customer Success
|
|
7606
|
+
"support_ticket",
|
|
7607
|
+
"customer_feedback",
|
|
7608
|
+
"churn_reason",
|
|
7609
|
+
"customer_health_score",
|
|
7610
|
+
"playbook",
|
|
7611
|
+
"service_level_agreement",
|
|
7612
|
+
"success_milestone",
|
|
7613
|
+
"service_blueprint",
|
|
7614
|
+
"nps_campaign",
|
|
7615
|
+
// Localisation
|
|
7616
|
+
"locale",
|
|
7617
|
+
"locale_config",
|
|
7618
|
+
// Customer Education
|
|
7619
|
+
"education_program",
|
|
7620
|
+
// Partners & Ecosystem
|
|
7621
|
+
"partner_program",
|
|
7622
|
+
"api_ecosystem",
|
|
7623
|
+
"developer_portal",
|
|
7624
|
+
// Portfolio
|
|
7625
|
+
// (UPG-677) product no longer lists product_area as a child: the
|
|
7626
|
+
// product↔product_area containment is the single direction
|
|
7627
|
+
// product_area_contains_product (product_area → product). The inverse
|
|
7628
|
+
// product_categorised_in_product_area edge was collapsed.
|
|
7629
|
+
// Workspace
|
|
7630
|
+
"workspace"
|
|
7631
|
+
],
|
|
7632
|
+
// ── Metric & Outcome hierarchy ──────────────────────────────────────────────
|
|
7633
|
+
outcome: ["metric", "opportunity", "feature"],
|
|
7634
|
+
// ── User & Jobs hierarchy ───────────────────────────────────────────────────
|
|
7635
|
+
persona: ["job", "need", "switching_cost", "desired_outcome"],
|
|
7636
|
+
job: ["need", "desired_outcome", "job_step"],
|
|
7637
|
+
// ── Discovery hierarchy ─────────────────────────────────────────────────────
|
|
7638
|
+
opportunity: ["solution", "design_concept", "feasibility_study", "design_sprint"],
|
|
7639
|
+
// solutions now propose hypothesis (the templated
|
|
7640
|
+
// belief), not the legacy hypothesis type.
|
|
7641
|
+
solution: ["hypothesis", "prototype", "metric"],
|
|
7642
|
+
// ── Validation hierarchy ────────────────────────────────────────────────────
|
|
7643
|
+
// (UPG-664) The validation chain is a single-parent line:
|
|
7644
|
+
// hypothesis ▷ experiment_plan ▷ experiment ▷ experiment_run.
|
|
7645
|
+
// hypothesis is canonical (hypothesis_evidence collapsed into evidence;
|
|
7646
|
+
// relationship expressed via hypothesis_has_evidence edge; evidence.direction
|
|
7647
|
+
// carries supports/refutes/neutral semantics). `test_plan` re-homed to the
|
|
7648
|
+
// QA/testing domain (UPG-678), so it is no longer a hypothesis child.
|
|
7649
|
+
hypothesis: ["experiment_plan", "research_plan", "evidence"],
|
|
7650
|
+
// experiment_plan is the canonical validation PLAN (graduated stable,
|
|
7651
|
+
// UPG-664). It designs the experiment it produces; the plan owns the
|
|
7652
|
+
// experiment in the containment line. It also retains experiment_run as a
|
|
7653
|
+
// child for the v0.2.6 split-migration path
|
|
7654
|
+
// (`experiment_plan_ran_as_experiment_run`), where a legacy experiment routes
|
|
7655
|
+
// directly to a plan + run pair.
|
|
7656
|
+
experiment_plan: ["experiment", "experiment_run"],
|
|
7657
|
+
// experiment is the canonical structured test. It owns its run(s) and the
|
|
7658
|
+
// learning/evidence it produces. The optional experiment_run child captures
|
|
7659
|
+
// the multi-run / replication case.
|
|
7660
|
+
experiment: ["experiment_run", "learning", "evidence"],
|
|
7661
|
+
// experiment_run carries learning/evidence/metric children (run produces
|
|
7662
|
+
// evidence) and self-nests for replications/multi-armed runs (V9: this is
|
|
7663
|
+
// where self-nesting genuinely lives, not on `experiment`).
|
|
7664
|
+
experiment_run: ["learning", "evidence", "metric", "experiment_run"],
|
|
7665
|
+
// ── OKR hierarchy ──────────────────────────────────────────────────────────
|
|
7666
|
+
// strategic_question (0.17.4): an open coordination question raised under an
|
|
7667
|
+
// objective (objective_raises_strategic_question, hierarchy).
|
|
7668
|
+
objective: ["key_result", "metric", "strategic_question"],
|
|
7669
|
+
key_result: ["metric"],
|
|
7670
|
+
// ── Strategic cascade: vision → mission → strategic_pillar → strategic_theme →
|
|
7671
|
+
// { initiative, objective } ───────────────────────────────────────────────
|
|
7672
|
+
//
|
|
7673
|
+
// strategic_pillar vs strategic_theme (UPG-692 T3.3 — distinct cascade levels,
|
|
7674
|
+
// NOT duplicates; sharpened rather than merged):
|
|
7675
|
+
// - strategic_pillar = a DURABLE structural division of the strategy. A
|
|
7676
|
+
// standing area the org organises around for years (e.g. "Structured
|
|
7677
|
+
// content", "Developer experience"). Few, long-lived; time_horizon is
|
|
7678
|
+
// multi-year / open-ended; carries its own `success_indicator`.
|
|
7679
|
+
// - strategic_theme = a TIME-BOUND thrust WITHIN a pillar. The current bet
|
|
7680
|
+
// for a period (e.g. "Go AI-native this year"). More numerous, rotates;
|
|
7681
|
+
// time_horizon is bounded (Q / FY); has NO `success_indicator` because it
|
|
7682
|
+
// is measured through its child objectives (strategic_theme_contains_objective).
|
|
7683
|
+
// That success-measurement asymmetry is the load-bearing distinction.
|
|
7684
|
+
vision: ["mission"],
|
|
7685
|
+
mission: ["strategic_pillar"],
|
|
7686
|
+
// 0.20.1: metric added as a strategic_pillar child, backing
|
|
7687
|
+
// strategic_pillar_measured_by_metric (hierarchy) — the pillar's own
|
|
7688
|
+
// north-star, mirrored from objective: ['key_result', 'metric', ...] one
|
|
7689
|
+
// level down the cascade.
|
|
7690
|
+
strategic_pillar: ["strategic_theme", "capability", "value_stream", "decision", "metric"],
|
|
7691
|
+
// v0.5.4 (UPG-511): objectives are the specific quarterly bets *within* a
|
|
7692
|
+
// strategic theme. The theme is the multi-quarter focus area; objectives are
|
|
7693
|
+
// subordinate. Pairs with `strategic_theme_contains_objective` in the edge
|
|
7694
|
+
// catalog (hierarchy, strategic_theme → objective, reverse_verb: rolls_up_to;
|
|
7695
|
+
// renamed from objective_rolls_up_to_strategic_theme in UPG-676).
|
|
7696
|
+
strategic_theme: ["initiative", "objective"],
|
|
7697
|
+
initiative: ["assumption", "strategic_question"],
|
|
7698
|
+
// v0.5.2 (UPG-528): capability decomposes into sub-capabilities (Wardley
|
|
7699
|
+
// value-chain spine) and is realised by user-facing features. Pairs with
|
|
7700
|
+
// `capability_depends_on_capability` and `capability_implemented_by_feature`
|
|
7701
|
+
// in the edge catalogue. The self-loop guard (UPG-520) keeps A → A refused;
|
|
7702
|
+
// A → B between distinct capabilities is the supported decomposition.
|
|
7703
|
+
capability: ["capability", "feature"],
|
|
7704
|
+
// ── Market hierarchy ────────────────────────────────────────────────────────
|
|
7705
|
+
// competitive_analysis hosts classification axes (its dimensions);
|
|
7706
|
+
// each axis hosts its values; values are reusable across the row and column
|
|
7707
|
+
// dossiers of a 2-axis matrix.
|
|
7708
|
+
competitive_analysis: ["competitor", "market_trend", "market_segment", "classification_axis"],
|
|
7709
|
+
competitor: ["competitor_feature", "competitor_signal"],
|
|
7710
|
+
classification_axis: ["classification_value"],
|
|
7711
|
+
// ── UX Research hierarchy ───────────────────────────────────────────────────
|
|
7712
|
+
// (0.35.0) 'quote' added: a study is the provenance parent of the verbatim
|
|
7713
|
+
// quotes it captured, pairing with `research_study_captures_quote`. A quote
|
|
7714
|
+
// now has three declared parents — observation (raw capture), insight
|
|
7715
|
+
// (synthesis) and research_study (provenance) — which is multi-parent
|
|
7716
|
+
// GRAMMAR; per-instance parentage stays single via `parent_id`.
|
|
7717
|
+
research_study: [
|
|
7718
|
+
"participant",
|
|
7719
|
+
"observation",
|
|
7720
|
+
"affinity_cluster",
|
|
7721
|
+
"research_question",
|
|
7722
|
+
"interview_guide",
|
|
7723
|
+
"insight",
|
|
7724
|
+
"survey_response",
|
|
7725
|
+
"quote"
|
|
7726
|
+
],
|
|
7727
|
+
observation: ["quote"],
|
|
7728
|
+
// F6 (UPG-672): affinity_cluster owns the observations it groups, in addition
|
|
7729
|
+
// to the insights it synthesises. Pairs with `affinity_cluster_groups_observation`
|
|
7730
|
+
// in the edge catalog (hierarchy). An observation can be both directly owned by
|
|
7731
|
+
// its research_study and grouped under an affinity_cluster (multi-parent grammar).
|
|
7732
|
+
affinity_cluster: ["insight", "observation"],
|
|
7733
|
+
// insight refines into insight; a raw insight can be distilled
|
|
7734
|
+
// into a more specific insight (refines_into chain).
|
|
7735
|
+
// v0.5.8 (UPG-528 Part 2d): insights own their evidencing quotes;
|
|
7736
|
+
// parallel to `observation: ['quote']`. Research-synthesis canon.
|
|
7737
|
+
// Pairs with `insight_evidenced_by_quote` in the edge catalog.
|
|
7738
|
+
// ── Design hierarchy ────────────────────────────────────────────────────────
|
|
7739
|
+
// (UPG-663, v0.9.2) A user_journey owns its steps (the stable 0.1.0 spine)
|
|
7740
|
+
// and carries phases as a non-owning band overlay. A journey_phase is NO
|
|
7741
|
+
// LONGER a containment parent of journey_step: it SPANS steps via the
|
|
7742
|
+
// `journey_phase_spans_journey_step` edge (mirroring the marketing
|
|
7743
|
+
// `customer_journey_stage_spans_journey_step` precedent), so each step has
|
|
7744
|
+
// exactly one containment parent and the journey has one canonical step list.
|
|
7745
|
+
user_journey: ["journey_step", "journey_phase"],
|
|
7746
|
+
journey_step: ["journey_action"],
|
|
7747
|
+
// v0.5.8 (UPG-528 Part 2d): insights own their evidencing quotes;
|
|
7748
|
+
// parallel to `observation: ['quote']`. Research-synthesis canon
|
|
7749
|
+
// (insight ← quote evidencing) extends quote's hierarchy parents to
|
|
7750
|
+
// both observation (raw-capture parent) and insight (synthesis parent).
|
|
7751
|
+
// Pairs with `insight_evidenced_by_quote` in the edge catalog.
|
|
7752
|
+
insight: ["design_question", "insight", "quote"],
|
|
7753
|
+
// v0.5.2 (UPG-528): need anchors a Wardley value chain; capabilities
|
|
7754
|
+
// fulfil the need at the chain top. Cross-domain hierarchy (need ∈ Design,
|
|
7755
|
+
// capability ∈ Strategy) is intentional: Wardley analysis starts from a
|
|
7756
|
+
// user need and decomposes into the capabilities required to fulfil it.
|
|
7757
|
+
need: ["design_question", "capability"],
|
|
7758
|
+
design_question: ["design_concept"],
|
|
7759
|
+
design_concept: ["prototype", "wireframe"],
|
|
7760
|
+
design_component: [
|
|
7761
|
+
"design_token",
|
|
7762
|
+
"design_pattern",
|
|
7763
|
+
"design_guideline",
|
|
7764
|
+
"interaction_spec",
|
|
7765
|
+
"design_component"
|
|
7766
|
+
],
|
|
7767
|
+
// self-nesting for atomic hierarchy (organism → molecule → atom)
|
|
7768
|
+
prototype: ["annotation"],
|
|
7769
|
+
// ── Brand hierarchy ─────────────────────────────────────────────────────────
|
|
7770
|
+
brand_identity: [
|
|
7771
|
+
"brand_colour",
|
|
7772
|
+
"brand_typography",
|
|
7773
|
+
"brand_asset",
|
|
7774
|
+
"brand_voice",
|
|
7775
|
+
"brand_logo",
|
|
7776
|
+
"brand_imagery"
|
|
7777
|
+
],
|
|
7778
|
+
// ── Product Specification hierarchy ─────────────────────────────────────────
|
|
7779
|
+
// story_task collapsed into canonical task. feature now owns
|
|
7780
|
+
// task directly; story-derived tasks implement user_story via
|
|
7781
|
+
// task_implements_user_story edge.
|
|
7782
|
+
// (0.35.0) 'user_story' added, pairing with `feature_specified_by_user_story`.
|
|
7783
|
+
// The epic rung is optional in UPG, so a feature that skips it could not
|
|
7784
|
+
// reach the stories that specify it and `user_story` had a single declared
|
|
7785
|
+
// parent. Same widening shape as the 0.23.0 epic twins: an existing leaf type
|
|
7786
|
+
// gains a second, SHALLOWER parent, verbs inherited from the deeper edge.
|
|
7787
|
+
feature: ["epic", "bug", "task", "user_story"],
|
|
7788
|
+
feature_area: ["feature", "feature_area"],
|
|
7789
|
+
// epic owns user_story (the templated promise) and, mirroring feature, may
|
|
7790
|
+
// also directly contain bug/task — heterogeneous imported tickets that belong
|
|
7791
|
+
// to one epic rather than the feature as a whole (feedback df99026a).
|
|
7792
|
+
epic: ["user_story", "bug", "task"],
|
|
7793
|
+
user_story: ["acceptance_criterion"],
|
|
7794
|
+
task: ["task"],
|
|
7795
|
+
// planning_cycle (0.20.0): the cadence axis self-nests (a program-increment
|
|
7796
|
+
// contains iterations; a cycle contains its cooldown) via
|
|
7797
|
+
// planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
|
|
7798
|
+
// child: scheduling work into a cycle is the semantic, deliberate-only
|
|
7799
|
+
// planning_cycle_schedules_work_item edge, so the item keeps its feature/epic
|
|
7800
|
+
// containment parent and is merely referenced by the cycle.
|
|
7801
|
+
planning_cycle: ["planning_cycle"],
|
|
7802
|
+
// ── Engineering hierarchy ───────────────────────────────────────────────────
|
|
7803
|
+
bounded_context: [
|
|
7804
|
+
"service",
|
|
7805
|
+
"domain_event",
|
|
7806
|
+
"decision",
|
|
7807
|
+
"data_model",
|
|
7808
|
+
"aggregate",
|
|
7809
|
+
"read_model",
|
|
7810
|
+
"code_repository",
|
|
7811
|
+
"integration_pattern",
|
|
7812
|
+
"external_api",
|
|
7813
|
+
"data_flow",
|
|
7814
|
+
// v0.5.7 (UPG-528 Part 2c): BCs publish api_contracts as their "published
|
|
7815
|
+
// language" (DDD canon). api_contract has two valid hierarchy parents now:
|
|
7816
|
+
// service (per-service exposure) AND bounded_context (BC-level surface).
|
|
7817
|
+
"api_contract"
|
|
7818
|
+
],
|
|
7819
|
+
service: [
|
|
7820
|
+
"api_contract",
|
|
7821
|
+
"technical_debt_item",
|
|
7822
|
+
"feature_flag",
|
|
7823
|
+
"deployment",
|
|
7824
|
+
"api_endpoint",
|
|
7825
|
+
"database_schema",
|
|
7826
|
+
"queue_topic",
|
|
7827
|
+
"build_artifact",
|
|
7828
|
+
"library_dependency"
|
|
7829
|
+
],
|
|
7830
|
+
// v0.5.1 (UPG-517 C2): contracts contain their endpoints. Service still
|
|
7831
|
+
// serves endpoints directly (legacy / non-contract endpoints); both
|
|
7832
|
+
// parent-child paths coexist.
|
|
7833
|
+
api_contract: ["api_endpoint"],
|
|
7834
|
+
decision: ["technical_debt_item"],
|
|
7835
|
+
aggregate: ["domain_entity", "value_object", "command"],
|
|
7836
|
+
// ── Growth hierarchy ────────────────────────────────────────────────────────
|
|
7837
|
+
funnel: ["funnel_step"],
|
|
7838
|
+
acquisition_channel: ["growth_campaign"],
|
|
7839
|
+
growth_campaign: ["experiment_plan", "variant"],
|
|
7840
|
+
// ── Business Model hierarchy ─────────────────────────────────────────────────
|
|
7841
|
+
business_model: [
|
|
7842
|
+
"value_proposition",
|
|
7843
|
+
"revenue_stream",
|
|
7844
|
+
"cost_structure",
|
|
7845
|
+
"unit_economics",
|
|
7846
|
+
"partnership",
|
|
7847
|
+
"key_resource",
|
|
7848
|
+
"key_activity",
|
|
7849
|
+
"customer_relationship",
|
|
7850
|
+
"distribution_channel"
|
|
7851
|
+
],
|
|
7852
|
+
revenue_stream: ["pricing_tier"],
|
|
7853
|
+
// ── Go-To-Market hierarchy ───────────────────────────────────────────────────
|
|
7854
|
+
gtm_strategy: [
|
|
7855
|
+
"ideal_customer_profile",
|
|
7856
|
+
"positioning",
|
|
7857
|
+
"launch",
|
|
7858
|
+
"content_strategy",
|
|
7859
|
+
"sales_motion",
|
|
7860
|
+
"competitive_battle_card",
|
|
7861
|
+
"demand_gen_program",
|
|
7862
|
+
"territory"
|
|
7863
|
+
],
|
|
7864
|
+
positioning: ["messaging", "objection", "proof_point"],
|
|
7865
|
+
value_proposition: ["objection", "proof_point"],
|
|
7866
|
+
competitive_battle_card: ["objection"],
|
|
7867
|
+
objection: ["rebuttal"],
|
|
7868
|
+
rebuttal: ["proof_point"],
|
|
7869
|
+
// ── Team & Organisation hierarchy ───────────────────────────────────────────
|
|
7870
|
+
department: ["team", "stakeholder"],
|
|
7871
|
+
team: [
|
|
7872
|
+
"role",
|
|
7873
|
+
"team_okr",
|
|
7874
|
+
"retrospective",
|
|
7875
|
+
"dependency",
|
|
7876
|
+
"skill",
|
|
7877
|
+
"ceremony",
|
|
7878
|
+
"capacity_plan",
|
|
7879
|
+
"decision",
|
|
7880
|
+
// Self-nesting (0.17.2): a parent team contains its sub-teams / squads, one
|
|
7881
|
+
// level below department_contains_team. Backs team_contains_team. Mirrors
|
|
7882
|
+
// feature_area_contains_feature_area (same-type child listed last).
|
|
7883
|
+
"team"
|
|
7884
|
+
],
|
|
7885
|
+
// ── Data & Analytics hierarchy ───────────────────────────────────────────────
|
|
7886
|
+
data_source: ["metric", "data_pipeline", "data_lineage", "event_schema"],
|
|
7887
|
+
// UPG-685 T0.4 (0.13.0 Wave 1): `outcome` removed as a metric child. It closed a
|
|
7888
|
+
// containment cycle (outcome ⊃ metric ⊃ outcome) and was backed only by the CAUSAL
|
|
7889
|
+
// `metric_drives_outcome` edge, not a hierarchy edge — the "metric leads to outcome"
|
|
7890
|
+
// meaning lives correctly on that causal edge. The real containment (outcome ⊃ metric)
|
|
7891
|
+
// is kept. Sub-metric trees (metric ⊃ metric) stay.
|
|
7892
|
+
metric: ["metric", "data_quality_rule", "metric_quality_assessment"],
|
|
7893
|
+
data_domain: ["data_product", "data_source", "glossary_term", "data_model", "dashboard"],
|
|
7894
|
+
dashboard: ["report", "experiment_run"],
|
|
7895
|
+
// ── Content & Knowledge hierarchy ────────────────────────────────────────────
|
|
7896
|
+
// release contains features and bugs (GitHub milestone→issue import).
|
|
7897
|
+
release: ["changelog", "feature", "bug"],
|
|
7898
|
+
content_strategy: ["content_calendar", "content_theme"],
|
|
7899
|
+
content_calendar: [
|
|
7900
|
+
"content_theme",
|
|
7901
|
+
"content_piece",
|
|
7902
|
+
"knowledge_base_article",
|
|
7903
|
+
"brand_asset",
|
|
7904
|
+
"document",
|
|
7905
|
+
"documentation_template"
|
|
7906
|
+
],
|
|
7907
|
+
// ── Operations & CS hierarchy ────────────────────────────────────────────────
|
|
7908
|
+
service_blueprint: [
|
|
7909
|
+
"user_flow",
|
|
7910
|
+
"playbook",
|
|
7911
|
+
"service_level_agreement",
|
|
7912
|
+
"customer_health_score",
|
|
7913
|
+
"support_ticket",
|
|
7914
|
+
"customer_feedback"
|
|
7915
|
+
],
|
|
7916
|
+
customer_health_score: ["nps_campaign", "success_milestone"],
|
|
7917
|
+
customer_feedback: ["churn_reason"],
|
|
7918
|
+
user_flow: ["screen", "customer_journey_stage"],
|
|
7919
|
+
customer_journey_stage: ["touchpoint"],
|
|
7920
|
+
// ── Legal, Compliance & Risk hierarchy ──────────────────────────────────────
|
|
7921
|
+
legal_entity: ["ip_asset", "contract"],
|
|
7922
|
+
contract: ["contract_clause"],
|
|
7923
|
+
compliance_framework: [
|
|
7924
|
+
"security_audit",
|
|
7925
|
+
"compliance_requirement",
|
|
7926
|
+
"privacy_policy",
|
|
7927
|
+
"audit_log_policy",
|
|
7928
|
+
"risk",
|
|
7929
|
+
"data_contract",
|
|
7930
|
+
"legal_entity"
|
|
7931
|
+
],
|
|
7932
|
+
// ── DevOps & Platform hierarchy ──────────────────────────────────────────────
|
|
7933
|
+
infrastructure_component: [
|
|
7934
|
+
"service_level_objective",
|
|
7935
|
+
"monitor",
|
|
7936
|
+
"ci_pipeline",
|
|
7937
|
+
"incident",
|
|
7938
|
+
"runbook",
|
|
7939
|
+
"release_strategy",
|
|
7940
|
+
"on_call_rotation"
|
|
7941
|
+
],
|
|
7942
|
+
service_level_objective: ["service_level_indicator", "error_budget"],
|
|
7943
|
+
incident: ["postmortem"],
|
|
7944
|
+
monitor: ["alert_rule"],
|
|
7945
|
+
ci_pipeline: ["build_artifact"],
|
|
7946
|
+
// ── Security hierarchy ───────────────────────────────────────────────────────
|
|
7947
|
+
security_policy: [
|
|
7948
|
+
"security_control",
|
|
7949
|
+
"access_policy",
|
|
7950
|
+
"data_classification",
|
|
7951
|
+
"threat_model",
|
|
7952
|
+
"security_review",
|
|
7953
|
+
"incident"
|
|
7954
|
+
],
|
|
7955
|
+
security_review: ["penetration_test"],
|
|
7956
|
+
threat_model: ["threat", "vulnerability"],
|
|
7957
|
+
// ── Sales & Revenue hierarchy ────────────────────────────────────────────────
|
|
7958
|
+
pipeline_sales: ["pipeline_stage", "lead", "account", "forecast", "subscription"],
|
|
7959
|
+
account: ["contact", "deal"],
|
|
7960
|
+
deal: ["quote_document"],
|
|
7961
|
+
subscription: ["invoice"],
|
|
7962
|
+
// ── Program Management hierarchy ─────────────────────────────────────────────
|
|
7963
|
+
program: ["project", "risk_register", "change_request", "resource_allocation", "status_report"],
|
|
7964
|
+
project: ["milestone", "deliverable"],
|
|
7965
|
+
risk_register: ["risk"],
|
|
7966
|
+
// ── Accessibility hierarchy ──────────────────────────────────────────────────
|
|
7967
|
+
a11y_standard: ["a11y_guideline", "a11y_audit", "a11y_annotation"],
|
|
7968
|
+
a11y_audit: ["a11y_issue"],
|
|
7969
|
+
// ── Product Specification expansion ─────────────────────────────────────────
|
|
7970
|
+
roadmap: ["roadmap_item", "roadmap_theme", "release"],
|
|
7971
|
+
roadmap_theme: ["feature"],
|
|
7972
|
+
// ── Unified Context Layer hierarchy ─────────────────────────────────────────
|
|
7973
|
+
design_system: [
|
|
7974
|
+
"design_component",
|
|
7975
|
+
"design_token",
|
|
7976
|
+
"design_guideline",
|
|
7977
|
+
"brand_identity",
|
|
7978
|
+
"user_journey",
|
|
7979
|
+
"user_flow",
|
|
7980
|
+
"insight"
|
|
7981
|
+
],
|
|
7982
|
+
screen: ["screen_state", "screen", "surface", "design_component", "wireframe"],
|
|
7983
|
+
// self-nesting + surfaces + components + wireframes
|
|
7984
|
+
// surface (0.27.0): the place inside a screen. Self-nests, so a shell holds
|
|
7985
|
+
// panes, a pane holds regions, a region holds slots. A surface is reached
|
|
7986
|
+
// through the screen that renders it (`screen: [... 'surface']`), never as a
|
|
7987
|
+
// top-level product child: every UPG_VALID_CHILDREN pair must be backed by a
|
|
7988
|
+
// hierarchy-classified edge (guardrail G2b), and the ratified ten do not
|
|
7989
|
+
// include a product-to-surface verb. An app-shell surface therefore hangs off
|
|
7990
|
+
// the screen that mounts it.
|
|
7991
|
+
//
|
|
7992
|
+
// NOTE: `surface_kind` narrows this further than UPG_VALID_CHILDREN can
|
|
7993
|
+
// express. UPG_VALID_CHILDREN is keyed on entity TYPE, so it can only say
|
|
7994
|
+
// "a surface may contain a surface"; the kind ordering (shell > tool > pane >
|
|
7995
|
+
// region > slot / gutter / action_bar, with overlay and ambient parented by
|
|
7996
|
+
// whatever raises them) is a property-level rule documented on `SurfaceKind`
|
|
7997
|
+
// and enforced by validators, not by this table. Precedent: the same split
|
|
7998
|
+
// applies to `planning_cycle`, whose `cadence_kind` narrows its self-nesting.
|
|
7999
|
+
surface: ["surface", "design_component"],
|
|
8000
|
+
// ── Marketing & Communications hierarchy ────────────────────────────────────
|
|
8001
|
+
marketing_strategy: [
|
|
8002
|
+
"marketing_channel",
|
|
8003
|
+
"seo_keyword",
|
|
8004
|
+
"press_release",
|
|
8005
|
+
"event",
|
|
8006
|
+
"community_initiative"
|
|
8007
|
+
],
|
|
8008
|
+
marketing_channel: ["marketing_campaign_plan"],
|
|
8009
|
+
marketing_campaign_plan: ["email_sequence", "social_post", "ad_creative"],
|
|
8010
|
+
// ── Localisation & i18n hierarchy ───────────────────────────────────────────
|
|
8011
|
+
locale: ["translation_bundle", "cultural_adaptation", "regional_pricing", "locale_config"],
|
|
8012
|
+
translation_bundle: ["translation_key"],
|
|
8013
|
+
// ── Customer Education & Training hierarchy ──────────────────────────────────
|
|
8014
|
+
education_program: [
|
|
8015
|
+
"tutorial",
|
|
8016
|
+
"walkthrough",
|
|
8017
|
+
"webinar",
|
|
8018
|
+
"certification",
|
|
8019
|
+
"help_video",
|
|
8020
|
+
"learning_path"
|
|
8021
|
+
],
|
|
8022
|
+
// a learning_path can include its terminal certification.
|
|
8023
|
+
learning_path: ["tutorial", "certification"],
|
|
8024
|
+
// ── Quality Assurance & Testing hierarchy ────────────────────────────────────
|
|
8025
|
+
// (UPG-678) test_plan re-homed validation → QA. It is the QA planning layer:
|
|
8026
|
+
// the verification approach (scope, environments, pass criteria) that the
|
|
8027
|
+
// test suites execute. A test_plan groups the suites that carry it out.
|
|
8028
|
+
test_plan: ["test_suite", "test_environment"],
|
|
8029
|
+
test_suite: [
|
|
8030
|
+
"test_case",
|
|
8031
|
+
"regression_test",
|
|
8032
|
+
"qa_session",
|
|
8033
|
+
"test_coverage_report",
|
|
8034
|
+
"test_environment",
|
|
8035
|
+
"test_result"
|
|
8036
|
+
],
|
|
8037
|
+
test_case: ["test_result"],
|
|
8038
|
+
qa_session: ["bug"],
|
|
8039
|
+
// ── Partner & Ecosystem Management hierarchy ─────────────────────────────────
|
|
8040
|
+
partner_program: [
|
|
8041
|
+
"partner_tier",
|
|
8042
|
+
"integration_partner",
|
|
8043
|
+
"partner_revenue_share",
|
|
8044
|
+
"api_ecosystem",
|
|
8045
|
+
"developer_portal"
|
|
8046
|
+
],
|
|
8047
|
+
api_ecosystem: ["marketplace_listing"],
|
|
8048
|
+
// ── Feedback & Voice of Customer hierarchy ───────────────────────────────────
|
|
8049
|
+
feedback_program: [
|
|
8050
|
+
"feature_request",
|
|
8051
|
+
"nps_campaign",
|
|
8052
|
+
"feedback_theme",
|
|
8053
|
+
"user_advisory_board",
|
|
8054
|
+
"beta_program"
|
|
8055
|
+
],
|
|
8056
|
+
feature_request: ["feedback_vote"],
|
|
8057
|
+
// v0.5.8 (UPG-528 Part 2d): CABs convene as quarterly ceremonies;
|
|
8058
|
+
// CAB owns its meeting cadence (Stettler, Moore CAB playbook canon).
|
|
8059
|
+
// Pairs with `user_advisory_board_convenes_as_ceremony` in the edge
|
|
8060
|
+
// catalog. ceremony now has two valid hierarchy parents (team for sprint
|
|
8061
|
+
// ceremonies, user_advisory_board for CAB meetings).
|
|
8062
|
+
user_advisory_board: ["ceremony"],
|
|
8063
|
+
// ── Pricing & Packaging hierarchy ────────────────────────────────────────────
|
|
8064
|
+
pricing_strategy: [
|
|
8065
|
+
"experiment_plan",
|
|
8066
|
+
"pricing_tier",
|
|
8067
|
+
"discount_strategy",
|
|
8068
|
+
"trial_config",
|
|
8069
|
+
"paywall"
|
|
8070
|
+
],
|
|
8071
|
+
// ── AI/ML Operations hierarchy ───────────────────────────────────────────────
|
|
8072
|
+
// (UPG-665) The prompt abstraction is corrected: ai_model → prompt_template →
|
|
8073
|
+
// prompt_version (a model has templates; each template has versions). The
|
|
8074
|
+
// model no longer owns prompt_version directly.
|
|
8075
|
+
ai_model: [
|
|
8076
|
+
"prompt_template",
|
|
8077
|
+
"eval_benchmark",
|
|
8078
|
+
"ai_cost_tracker",
|
|
8079
|
+
"hallucination_report",
|
|
8080
|
+
"ai_guardrail",
|
|
8081
|
+
"model_comparison",
|
|
8082
|
+
"ai_experiment",
|
|
8083
|
+
"ai_dataset",
|
|
8084
|
+
"ai_trace"
|
|
8085
|
+
],
|
|
8086
|
+
// prompt_template owns its prompt_versions the way a file owns its commits.
|
|
8087
|
+
prompt_template: ["prompt_version"],
|
|
8088
|
+
// v0.5.7 (UPG-528 Part 2c): benchmarks define the metric set they measure
|
|
8089
|
+
// (HELM, MLPerf, BIG-bench all spec a metric list). metric already has many
|
|
8090
|
+
// hierarchy parents (outcome, objective, key_result, solution, data_source).
|
|
8091
|
+
eval_benchmark: ["eval_run", "metric"],
|
|
8092
|
+
// ai_trace spawns child traces for sub-calls (tool calls, chained
|
|
8093
|
+
// prompts). Self-nesting enables call-tree representation.
|
|
8094
|
+
ai_trace: ["ai_trace"],
|
|
8095
|
+
// ── Agentic Workflows & Process hierarchy ────────────────────────────────────
|
|
8096
|
+
workflow_template: ["workflow_run", "review_gate", "agent_task"],
|
|
8097
|
+
workflow_run: ["workflow_artifact"],
|
|
8098
|
+
agent_definition: ["agent_session", "agent_skill", "agent_hook", "workflow_template", "agent_task"],
|
|
8099
|
+
// v0.5.8 (UPG-528 Part 2d): review gates also vet research insights
|
|
8100
|
+
// (ResearchOps insight-review pattern). Pairs with `review_gate_vets_insight`
|
|
8101
|
+
// in the edge catalog. The gate now owns two child types: approval records
|
|
8102
|
+
// for workflow gates, insights for research-democratisation gates.
|
|
8103
|
+
review_gate: ["approval_record", "insight"],
|
|
8104
|
+
// ── Investigation hierarchy ──────────────────────────────────────────────────
|
|
8105
|
+
investigation: ["symptom", "root_cause"],
|
|
8106
|
+
root_cause: ["fix"],
|
|
8107
|
+
// ── Portfolio layer hierarchy ────────────────────────────────────────────────
|
|
8108
|
+
// organization → product_area is the org-axis anchor (who owns what);
|
|
8109
|
+
// organization → portfolio is the strategic-axis anchor (where you invest).
|
|
8110
|
+
// organization → workspace (WS3, 2026-07-05): org-altitude workspace anchor
|
|
8111
|
+
// (organization_thinks_in_workspace) — a workspace scoped to org altitude
|
|
8112
|
+
// lives in the same portfolio-level graph as its organization node.
|
|
8113
|
+
organization: ["portfolio", "product_area", "workspace"],
|
|
8114
|
+
// portfolios can nest (multi-level investment structures).
|
|
8115
|
+
portfolio: ["product", "portfolio"],
|
|
8116
|
+
// product_area is the organisational container for products and can
|
|
8117
|
+
// nest (parent → sub-area). product_area also groups features; the
|
|
8118
|
+
// "Studio area owns 6 features" mental model. product_area → workspace
|
|
8119
|
+
// (WS3, 2026-07-05): plane-altitude workspace anchor
|
|
8120
|
+
// (product_area_thinks_in_workspace) — "plane" is Entopo's app-level name
|
|
8121
|
+
// for product_area, not a distinct spec entity.
|
|
8122
|
+
product_area: ["product", "product_area", "feature", "workspace"]
|
|
8123
|
+
};
|
|
8124
|
+
function getValidChildren(entityType) {
|
|
8125
|
+
return UPG_VALID_CHILDREN[entityType] ?? [];
|
|
8126
|
+
}
|
|
8127
|
+
function canBeChildOf(childType, parentType) {
|
|
8128
|
+
return getValidChildren(parentType).includes(childType);
|
|
8129
|
+
}
|
|
8130
|
+
var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
7682
8131
|
"person",
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
//
|
|
7687
|
-
//
|
|
7688
|
-
"
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
//
|
|
7692
|
-
//
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
//
|
|
7696
|
-
"
|
|
7697
|
-
|
|
7698
|
-
//
|
|
7699
|
-
//
|
|
7700
|
-
|
|
7701
|
-
"funnel_step",
|
|
7702
|
-
"acquisition_channel",
|
|
7703
|
-
"cohort",
|
|
7704
|
-
"behavioral_segment",
|
|
7705
|
-
"growth_loop",
|
|
7706
|
-
"attribution_model",
|
|
7707
|
-
// ── Sales (5 of 6): pipeline structure + accounts/contacts + forecast
|
|
7708
|
-
// snapshots. Only `deal` has a lifecycle (Phase B SALES_DEAL template) ─
|
|
7709
|
-
"account",
|
|
7710
|
-
"contact",
|
|
7711
|
-
"pipeline_sales",
|
|
7712
|
-
"pipeline_stage",
|
|
7713
|
-
"forecast",
|
|
7714
|
-
// ── Customer Success (5 of 9): journey structure + derived values ────────
|
|
7715
|
-
"customer_journey_stage",
|
|
7716
|
-
"touchpoint",
|
|
7717
|
-
"churn_reason",
|
|
7718
|
-
"service_level_agreement",
|
|
7719
|
-
// ── DevOps (5 of 10): indicators and budgets are measurement references;
|
|
7720
|
-
// strategies and pipelines are configuration ──────────────────────────
|
|
7721
|
-
"service_level_indicator",
|
|
7722
|
-
"service_level_objective",
|
|
7723
|
-
"error_budget",
|
|
7724
|
-
"ci_pipeline",
|
|
7725
|
-
"release_strategy",
|
|
7726
|
-
// ── Security (1 of 5): data classification is a reference label ──────────
|
|
7727
|
-
"data_classification",
|
|
7728
|
-
// ── Accessibility (2 of 4): standards and annotations ─────────────────────
|
|
7729
|
-
"a11y_standard",
|
|
7730
|
-
"a11y_annotation",
|
|
7731
|
-
// ── Testing (3 of 5): regression tests are reference definitions; test
|
|
7732
|
-
// results and coverage reports are immutable records ───────────────────
|
|
7733
|
-
"regression_test",
|
|
7734
|
-
"test_coverage_report",
|
|
7735
|
-
"test_result",
|
|
7736
|
-
// ── Customer Feedback (2 of 3): themes and votes are atomic data points ──
|
|
7737
|
-
"feedback_theme",
|
|
7738
|
-
"feedback_vote",
|
|
7739
|
-
// ── Pricing (2 of 4): trial and paywall are configuration ─────────────────
|
|
7740
|
-
"trial_config",
|
|
7741
|
-
"paywall",
|
|
7742
|
-
// ── Content (2 of 5): calendars and themes are planning configuration ────
|
|
7743
|
-
"content_calendar",
|
|
7744
|
-
"content_theme",
|
|
7745
|
-
// ── Legal (2 of 3): legal_entity is an org; contract_clause is an atom
|
|
7746
|
-
// of a contract (which has its own lifecycle) ───────────────────────────
|
|
7747
|
-
"legal_entity",
|
|
7748
|
-
"contract_clause",
|
|
7749
|
-
// ── Portfolio (3 of 3): structural containers ─────────────────────────────
|
|
7750
|
-
"organization",
|
|
7751
|
-
"portfolio",
|
|
7752
|
-
"product_area",
|
|
7753
|
-
// ── Program Management (2 of 3): allocations and registers are tracking
|
|
7754
|
-
// containers ────────────────────────────────────────────────────────────
|
|
7755
|
-
"resource_allocation",
|
|
7756
|
-
"risk_register",
|
|
7757
|
-
// ── Localisation (4 of 5): translation keys, configs, and regional
|
|
7758
|
-
// adaptations are reference data ────────────────────────────────────────
|
|
7759
|
-
"translation_key",
|
|
7760
|
-
"locale_config",
|
|
7761
|
-
"cultural_adaptation",
|
|
7762
|
-
"regional_pricing",
|
|
7763
|
-
// ── Marketing (1 of 4): SEO keywords are tracked reference terms ─────────
|
|
7764
|
-
"seo_keyword",
|
|
7765
|
-
// ── Ecosystem (2 of 2): partner tiers and rev-share terms are config ─────
|
|
7766
|
-
"partner_tier",
|
|
7767
|
-
"partner_revenue_share",
|
|
7768
|
-
// ── Validation (2 of 4): evidence + learning are data points captured
|
|
7769
|
-
// from experiment_runs (which have lifecycles); hypothesis_evidence was
|
|
7770
|
-
// lifecycle-free here but is deprecated at v0.4.0 ──────────────────────────────────────
|
|
7771
|
-
"evidence",
|
|
7772
|
-
"learning",
|
|
7773
|
-
// ── Workspace (1 of 4): a capture is a fact about a moment, not a workflow.
|
|
7774
|
-
// It is taken, and from then on it either still matches its subject or it
|
|
7775
|
-
// does not — which is what `content_hash` answers, not a phase. ─────────
|
|
8132
|
+
// A framework_exercise has no structural parent: it anchors to the entities
|
|
8133
|
+
// it scores via the `framework_exercise_includes_node` edge, not by
|
|
8134
|
+
// containment. Modelling it as a product child would force a containment edge
|
|
8135
|
+
// and read against the whole point of the exercise model (relational, not
|
|
8136
|
+
// hierarchical). Same posture as `person`. See ADR 2026-06-02-framework-exercises.
|
|
8137
|
+
"framework_exercise",
|
|
8138
|
+
// A composition has no structural parent for the same reason a
|
|
8139
|
+
// framework_exercise does not: it anchors to what it SHOWS, relationally, via
|
|
8140
|
+
// `composition_focuses_node`. Modelling it as a product child would force a
|
|
8141
|
+
// containment edge that reads against the point — a published view is not a
|
|
8142
|
+
// part of the product, it is a lens onto one. This is also what keeps the
|
|
8143
|
+
// type to two edges rather than three: no provenance edge is minted (that is
|
|
8144
|
+
// `workspace_produced_node`, reused) and no parent edge is needed.
|
|
8145
|
+
"composition",
|
|
8146
|
+
// A capture has no structural parent for the same reason: it anchors to what
|
|
8147
|
+
// it RENDERS, via `capture_renders_node`. Giving it a product parent would
|
|
8148
|
+
// claim a capture is part of the product rather than a picture of one, and
|
|
8149
|
+
// would mint a containment edge whose only job is to satisfy the hierarchy.
|
|
7776
8150
|
"capture"
|
|
7777
8151
|
]);
|
|
7778
|
-
function
|
|
7779
|
-
return
|
|
8152
|
+
function isContainmentFreeType(entityType) {
|
|
8153
|
+
return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
|
|
7780
8154
|
}
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
|
|
7794
|
-
return UPG_LIFECYCLE_PLANNED_TYPES.has(entityType);
|
|
8155
|
+
|
|
8156
|
+
// src/grammar/projection.ts
|
|
8157
|
+
var VARIES_BY_EDGE = "surface_varies_by_configuration_axis";
|
|
8158
|
+
var PRESENT_UNDER_PROPERTY = "present_under";
|
|
8159
|
+
var ACTIVE_WHEN_PROPERTY = "active_when";
|
|
8160
|
+
var QUALIFIABLE_EDGE_TYPES = [
|
|
8161
|
+
"surface_contains_surface",
|
|
8162
|
+
"feature_occupies_surface"
|
|
8163
|
+
];
|
|
8164
|
+
function readStringArray(properties, key) {
|
|
8165
|
+
const raw = properties?.[key];
|
|
8166
|
+
if (!Array.isArray(raw)) return void 0;
|
|
8167
|
+
return raw.filter((v) => typeof v === "string");
|
|
7795
8168
|
}
|
|
7796
|
-
function
|
|
7797
|
-
const
|
|
7798
|
-
if (!
|
|
7799
|
-
const
|
|
7800
|
-
const
|
|
7801
|
-
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
|
|
7806
|
-
|
|
7807
|
-
const
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
8169
|
+
function readActiveWhen(edge) {
|
|
8170
|
+
const raw = edge.properties?.[ACTIVE_WHEN_PROPERTY];
|
|
8171
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
8172
|
+
const axis = raw.axis;
|
|
8173
|
+
const values = readStringArray(raw, "values");
|
|
8174
|
+
if (typeof axis !== "string" || axis.length === 0 || !values || values.length === 0) {
|
|
8175
|
+
return void 0;
|
|
8176
|
+
}
|
|
8177
|
+
return { axis, values };
|
|
8178
|
+
}
|
|
8179
|
+
function projectGraph(nodes, edges, configuration) {
|
|
8180
|
+
const axes = Object.keys(configuration);
|
|
8181
|
+
if (axes.length === 0) {
|
|
8182
|
+
return {
|
|
8183
|
+
nodes: [...nodes],
|
|
8184
|
+
edges: [...edges],
|
|
8185
|
+
excluded_node_ids: [],
|
|
8186
|
+
deactivated_edge_count: 0,
|
|
8187
|
+
dangling_edge_count: 0
|
|
8188
|
+
};
|
|
8189
|
+
}
|
|
8190
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
8191
|
+
for (const edge of edges) {
|
|
8192
|
+
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
8193
|
+
const chosen = configuration[edge.target];
|
|
8194
|
+
if (chosen === void 0) continue;
|
|
8195
|
+
const presentUnder = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY);
|
|
8196
|
+
if (!presentUnder) continue;
|
|
8197
|
+
if (!presentUnder.includes(chosen)) excluded.add(edge.source);
|
|
8198
|
+
}
|
|
8199
|
+
const survivingNodes = excluded.size === 0 ? [...nodes] : nodes.filter((n) => !excluded.has(n.id));
|
|
8200
|
+
let deactivated = 0;
|
|
8201
|
+
let dangling = 0;
|
|
8202
|
+
const survivingEdges = [];
|
|
8203
|
+
for (const edge of edges) {
|
|
8204
|
+
const qualifier = readActiveWhen(edge);
|
|
8205
|
+
if (qualifier) {
|
|
8206
|
+
const chosen = configuration[qualifier.axis];
|
|
8207
|
+
if (chosen !== void 0 && !qualifier.values.includes(chosen)) {
|
|
8208
|
+
deactivated++;
|
|
8209
|
+
continue;
|
|
7821
8210
|
}
|
|
7822
|
-
transitions.push({ from: phase.id, to, kind });
|
|
7823
8211
|
}
|
|
8212
|
+
if (excluded.has(edge.source) || excluded.has(edge.target)) {
|
|
8213
|
+
dangling++;
|
|
8214
|
+
continue;
|
|
8215
|
+
}
|
|
8216
|
+
survivingEdges.push(edge);
|
|
7824
8217
|
}
|
|
7825
8218
|
return {
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
8219
|
+
nodes: survivingNodes,
|
|
8220
|
+
edges: survivingEdges,
|
|
8221
|
+
excluded_node_ids: [...excluded].sort(),
|
|
8222
|
+
deactivated_edge_count: deactivated,
|
|
8223
|
+
dangling_edge_count: dangling
|
|
7831
8224
|
};
|
|
7832
8225
|
}
|
|
8226
|
+
function enumerateProjections(nodes) {
|
|
8227
|
+
const out = [
|
|
8228
|
+
{ configuration: {} }
|
|
8229
|
+
];
|
|
8230
|
+
for (const node of nodes) {
|
|
8231
|
+
if (node.type !== "configuration_axis") continue;
|
|
8232
|
+
const values = readStringArray(node.properties, "values");
|
|
8233
|
+
if (!values) continue;
|
|
8234
|
+
for (const value of values) {
|
|
8235
|
+
out.push({ axis: node.id, value, configuration: { [node.id]: value } });
|
|
8236
|
+
}
|
|
8237
|
+
}
|
|
8238
|
+
return out;
|
|
8239
|
+
}
|
|
8240
|
+
|
|
8241
|
+
// src/grammar/configuration-drift.ts
|
|
8242
|
+
var ALTERNATES_EDGE = "surface_alternates_with_surface";
|
|
8243
|
+
var PARENT_EDGE_TYPES = /* @__PURE__ */ new Set(["surface_contains_surface", "screen_renders_surface"]);
|
|
8244
|
+
function edgeLabel(edge) {
|
|
8245
|
+
return edge.id ?? `${edge.source} -> ${edge.target}`;
|
|
8246
|
+
}
|
|
8247
|
+
function checkConfigurationDrift(nodes, edges) {
|
|
8248
|
+
const findings = [];
|
|
8249
|
+
const axisValues = /* @__PURE__ */ new Map();
|
|
8250
|
+
for (const node of nodes) {
|
|
8251
|
+
if (node.type !== "configuration_axis") continue;
|
|
8252
|
+
const values = readStringArray(node.properties, "values");
|
|
8253
|
+
if (!values || values.length === 0) {
|
|
8254
|
+
findings.push({
|
|
8255
|
+
kind: "axis_values_empty",
|
|
8256
|
+
severity: "error",
|
|
8257
|
+
node_id: node.id,
|
|
8258
|
+
axis_id: node.id,
|
|
8259
|
+
message: "Configuration axis declares no values. An axis with no values selects nothing, so no projection can be taken along it."
|
|
8260
|
+
});
|
|
8261
|
+
axisValues.set(node.id, []);
|
|
8262
|
+
continue;
|
|
8263
|
+
}
|
|
8264
|
+
axisValues.set(node.id, values);
|
|
8265
|
+
const rawDefault = node.properties?.default_value;
|
|
8266
|
+
if (typeof rawDefault === "string" && rawDefault.length > 0 && !values.includes(rawDefault)) {
|
|
8267
|
+
findings.push({
|
|
8268
|
+
kind: "axis_default_not_a_value",
|
|
8269
|
+
severity: "error",
|
|
8270
|
+
node_id: node.id,
|
|
8271
|
+
axis_id: node.id,
|
|
8272
|
+
message: `default_value "${rawDefault}" is not one of the axis values [${values.join(", ")}]. It names a value of this axis, so it has to be one the axis declares.`
|
|
8273
|
+
});
|
|
8274
|
+
}
|
|
8275
|
+
}
|
|
8276
|
+
for (const edge of edges) {
|
|
8277
|
+
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
8278
|
+
const values = axisValues.get(edge.target);
|
|
8279
|
+
const presentUnder = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY);
|
|
8280
|
+
if (!presentUnder || presentUnder.length === 0) {
|
|
8281
|
+
findings.push({
|
|
8282
|
+
kind: "present_under_empty",
|
|
8283
|
+
severity: "error",
|
|
8284
|
+
edge_id: edge.id,
|
|
8285
|
+
node_id: edge.source,
|
|
8286
|
+
axis_id: edge.target,
|
|
8287
|
+
message: `${edgeLabel(edge)} declares variance without a non-empty present_under. A surface that exists under no configuration should be deleted rather than declared; a surface that exists under all of them should carry no varies_by edge at all.`
|
|
8288
|
+
});
|
|
8289
|
+
continue;
|
|
8290
|
+
}
|
|
8291
|
+
if (!values) continue;
|
|
8292
|
+
for (const value of presentUnder) {
|
|
8293
|
+
if (!values.includes(value)) {
|
|
8294
|
+
findings.push({
|
|
8295
|
+
kind: "present_under_unknown_value",
|
|
8296
|
+
severity: "error",
|
|
8297
|
+
edge_id: edge.id,
|
|
8298
|
+
node_id: edge.source,
|
|
8299
|
+
axis_id: edge.target,
|
|
8300
|
+
message: `present_under names "${value}", which the axis does not declare. Its values are [${values.join(", ")}].`
|
|
8301
|
+
});
|
|
8302
|
+
}
|
|
8303
|
+
}
|
|
8304
|
+
}
|
|
8305
|
+
for (const edge of edges) {
|
|
8306
|
+
const hasQualifierKey = edge.properties !== void 0 && Object.prototype.hasOwnProperty.call(edge.properties, ACTIVE_WHEN_PROPERTY);
|
|
8307
|
+
if (!hasQualifierKey) continue;
|
|
8308
|
+
if (edge.type !== void 0 && !QUALIFIABLE_EDGE_TYPES.includes(edge.type)) {
|
|
8309
|
+
findings.push({
|
|
8310
|
+
kind: "qualifier_on_illegal_edge",
|
|
8311
|
+
severity: "error",
|
|
8312
|
+
edge_id: edge.id,
|
|
8313
|
+
message: `active_when is not legal on ${edge.type}. The qualifier is scoped to [${QUALIFIABLE_EDGE_TYPES.join(", ")}] deliberately: conditional composition is the question this release answers, and a general modality system is not.`
|
|
8314
|
+
});
|
|
8315
|
+
continue;
|
|
8316
|
+
}
|
|
8317
|
+
const qualifier = readActiveWhen(edge);
|
|
8318
|
+
if (!qualifier) {
|
|
8319
|
+
const rawQualifier = edge.properties?.[ACTIVE_WHEN_PROPERTY];
|
|
8320
|
+
const rawValues = rawQualifier && typeof rawQualifier === "object" && !Array.isArray(rawQualifier) ? rawQualifier.values : void 0;
|
|
8321
|
+
if (Array.isArray(rawValues) && rawValues.length === 0) {
|
|
8322
|
+
findings.push({
|
|
8323
|
+
kind: "qualifier_values_empty",
|
|
8324
|
+
severity: "error",
|
|
8325
|
+
edge_id: edge.id,
|
|
8326
|
+
message: `${edgeLabel(edge)} carries an active_when with an empty values list. Read literally that would remove the relationship from every configuration, which is a way to delete an edge by writing a property; it is treated as absent instead. To say a relationship never holds, delete the edge.`
|
|
8327
|
+
});
|
|
8328
|
+
continue;
|
|
8329
|
+
}
|
|
8330
|
+
findings.push({
|
|
8331
|
+
kind: "qualifier_axis_unresolved",
|
|
8332
|
+
severity: "error",
|
|
8333
|
+
edge_id: edge.id,
|
|
8334
|
+
message: `${edgeLabel(edge)} carries a malformed active_when. It must be an object with an axis id and a non-empty values array; anything else reads as absent, which silently makes the relationship invariant.`
|
|
8335
|
+
});
|
|
8336
|
+
continue;
|
|
8337
|
+
}
|
|
8338
|
+
const values = axisValues.get(qualifier.axis);
|
|
8339
|
+
if (!values) {
|
|
8340
|
+
findings.push({
|
|
8341
|
+
kind: "qualifier_axis_unresolved",
|
|
8342
|
+
severity: "error",
|
|
8343
|
+
edge_id: edge.id,
|
|
8344
|
+
axis_id: qualifier.axis,
|
|
8345
|
+
message: `active_when names axis "${qualifier.axis}", which is not a configuration_axis node in this graph.`
|
|
8346
|
+
});
|
|
8347
|
+
continue;
|
|
8348
|
+
}
|
|
8349
|
+
for (const value of qualifier.values) {
|
|
8350
|
+
if (!values.includes(value)) {
|
|
8351
|
+
findings.push({
|
|
8352
|
+
kind: "qualifier_unknown_value",
|
|
8353
|
+
severity: "error",
|
|
8354
|
+
edge_id: edge.id,
|
|
8355
|
+
axis_id: qualifier.axis,
|
|
8356
|
+
message: `active_when names value "${value}", which axis "${qualifier.axis}" does not declare. Its values are [${values.join(", ")}].`
|
|
8357
|
+
});
|
|
8358
|
+
}
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8361
|
+
const varianceBySource = /* @__PURE__ */ new Map();
|
|
8362
|
+
for (const edge of edges) {
|
|
8363
|
+
if (edge.type !== VARIES_BY_EDGE) continue;
|
|
8364
|
+
const values = readStringArray(edge.properties, PRESENT_UNDER_PROPERTY) ?? [];
|
|
8365
|
+
const list = varianceBySource.get(edge.source) ?? [];
|
|
8366
|
+
list.push({ axis: edge.target, values });
|
|
8367
|
+
varianceBySource.set(edge.source, list);
|
|
8368
|
+
}
|
|
8369
|
+
for (const edge of edges) {
|
|
8370
|
+
if (edge.type !== ALTERNATES_EDGE) continue;
|
|
8371
|
+
const left = varianceBySource.get(edge.source) ?? [];
|
|
8372
|
+
const right = varianceBySource.get(edge.target) ?? [];
|
|
8373
|
+
if (left.length === 0 || right.length === 0) {
|
|
8374
|
+
findings.push({
|
|
8375
|
+
kind: "alternation_axis_mismatch",
|
|
8376
|
+
severity: "error",
|
|
8377
|
+
edge_id: edge.id,
|
|
8378
|
+
message: `${edgeLabel(edge)} declares an alternation, but at least one endpoint declares no variance at all. Two surfaces that are both always present are not alternatives; they co-exist.`
|
|
8379
|
+
});
|
|
8380
|
+
continue;
|
|
8381
|
+
}
|
|
8382
|
+
const sharedAxes = left.filter((l) => right.some((r) => r.axis === l.axis)).map((l) => l.axis);
|
|
8383
|
+
if (sharedAxes.length === 0) {
|
|
8384
|
+
findings.push({
|
|
8385
|
+
kind: "alternation_axis_mismatch",
|
|
8386
|
+
severity: "error",
|
|
8387
|
+
edge_id: edge.id,
|
|
8388
|
+
message: `${edgeLabel(edge)} declares an alternation between surfaces that vary on different axes. Alternatives are selected by ONE lever; surfaces on different levers can both be present at once.`
|
|
8389
|
+
});
|
|
8390
|
+
continue;
|
|
8391
|
+
}
|
|
8392
|
+
for (const axis of sharedAxes) {
|
|
8393
|
+
const leftValues = left.find((l) => l.axis === axis)?.values ?? [];
|
|
8394
|
+
const rightValues = right.find((r) => r.axis === axis)?.values ?? [];
|
|
8395
|
+
const overlap = leftValues.filter((v) => rightValues.includes(v));
|
|
8396
|
+
if (overlap.length > 0) {
|
|
8397
|
+
findings.push({
|
|
8398
|
+
kind: "alternation_overlap",
|
|
8399
|
+
severity: "error",
|
|
8400
|
+
edge_id: edge.id,
|
|
8401
|
+
axis_id: axis,
|
|
8402
|
+
message: `${edgeLabel(edge)} declares an alternation, but both surfaces are present under [${overlap.join(", ")}] on axis "${axis}". They co-exist there, so the graph is asserting "one of these" and "both, together" about the same pair.`
|
|
8403
|
+
});
|
|
8404
|
+
}
|
|
8405
|
+
}
|
|
8406
|
+
}
|
|
8407
|
+
const parentsByChild = /* @__PURE__ */ new Map();
|
|
8408
|
+
for (const edge of edges) {
|
|
8409
|
+
if (edge.type && PARENT_EDGE_TYPES.has(edge.type)) {
|
|
8410
|
+
parentsByChild.set(edge.target, (parentsByChild.get(edge.target) ?? 0) + 1);
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8413
|
+
for (const [axisId, values] of axisValues) {
|
|
8414
|
+
for (const value of values) {
|
|
8415
|
+
const projected = projectGraph(nodes, edges, { [axisId]: value });
|
|
8416
|
+
const survivingParents = /* @__PURE__ */ new Map();
|
|
8417
|
+
for (const edge of projected.edges) {
|
|
8418
|
+
if (edge.type && PARENT_EDGE_TYPES.has(edge.type)) {
|
|
8419
|
+
survivingParents.set(edge.target, (survivingParents.get(edge.target) ?? 0) + 1);
|
|
8420
|
+
}
|
|
8421
|
+
}
|
|
8422
|
+
for (const node of projected.nodes) {
|
|
8423
|
+
if (node.type !== "surface") continue;
|
|
8424
|
+
if ((parentsByChild.get(node.id) ?? 0) === 0) continue;
|
|
8425
|
+
if ((survivingParents.get(node.id) ?? 0) > 0) continue;
|
|
8426
|
+
findings.push({
|
|
8427
|
+
kind: "orphaned_under_projection",
|
|
8428
|
+
severity: "warning",
|
|
8429
|
+
node_id: node.id,
|
|
8430
|
+
axis_id: axisId,
|
|
8431
|
+
message: `Surface is present under ${axisId} = "${value}" but every containment parent it has is absent there. Either it needs a parent in that configuration, or it should declare that it is absent from it too.`
|
|
8432
|
+
});
|
|
8433
|
+
}
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8436
|
+
return findings;
|
|
8437
|
+
}
|
|
7833
8438
|
|
|
7834
8439
|
// src/grammar/scales.ts
|
|
7835
8440
|
var UPG_SCALES = {
|
|
@@ -7965,6 +8570,37 @@ var UPG_SCALES = {
|
|
|
7965
8570
|
// the existing classify-edge population, so nothing needs re-stamping.
|
|
7966
8571
|
friendly_aliases: { low: 2, medium: 3, high: 4 }
|
|
7967
8572
|
},
|
|
8573
|
+
// ── Likelihood ─────────────────────────────────────────────────────────────
|
|
8574
|
+
/**
|
|
8575
|
+
* (0.35.0) How likely a future event is to occur.
|
|
8576
|
+
*
|
|
8577
|
+
* Added because the nine pre-existing ladders had no probability ladder, and
|
|
8578
|
+
* `likelihood` was resolving to `confidence_5` — which is EPISTEMIC. Confidence
|
|
8579
|
+
* says how sure you are of a judgment ("Guessing → Data-backed"); likelihood
|
|
8580
|
+
* says how probable the event is. A risk you are certain about and a risk that
|
|
8581
|
+
* is certain to happen are different facts, and one ladder cannot hold both.
|
|
8582
|
+
*
|
|
8583
|
+
* Vocabulary is ISO 31000's, which is also what `risk.likelihood`,
|
|
8584
|
+
* `threat.likelihood` and the Notion fixtures already say in prose.
|
|
8585
|
+
*
|
|
8586
|
+
* Polarity is `low-is-good` (rendered by the design system's direction map,
|
|
8587
|
+
* which is where numeric-scale polarity lives): 5 on this ladder is bad news.
|
|
8588
|
+
*/
|
|
8589
|
+
likelihood_5: {
|
|
8590
|
+
id: "likelihood_5",
|
|
8591
|
+
label: "Likelihood (5-point)",
|
|
8592
|
+
description: "How likely this event is to occur",
|
|
8593
|
+
min: 1,
|
|
8594
|
+
max: 5,
|
|
8595
|
+
steps: 5,
|
|
8596
|
+
points: [
|
|
8597
|
+
{ value: 1, label: "Rare", description: "Would be surprising; no known precedent" },
|
|
8598
|
+
{ value: 2, label: "Unlikely", description: "Could happen, but not expected" },
|
|
8599
|
+
{ value: 3, label: "Possible", description: "Might happen; roughly even odds" },
|
|
8600
|
+
{ value: 4, label: "Likely", description: "Expected to happen absent intervention" },
|
|
8601
|
+
{ value: 5, label: "Almost certain", description: "Expected to happen; plan for it" }
|
|
8602
|
+
]
|
|
8603
|
+
},
|
|
7968
8604
|
// ── Effort ─────────────────────────────────────────────────────────────────
|
|
7969
8605
|
effort_5: {
|
|
7970
8606
|
id: "effort_5",
|
|
@@ -8037,12 +8673,17 @@ var PROPERTY_SCALE_MAP = {
|
|
|
8037
8673
|
// discovery, validation, sales, product-spec
|
|
8038
8674
|
current_confidence: "confidence_5",
|
|
8039
8675
|
// validation/hypothesis (UPG-579 Option B)
|
|
8040
|
-
likelihood: "confidence_5",
|
|
8041
|
-
// market/risk (UPG-579 Option B)
|
|
8042
8676
|
probability: "confidence_5",
|
|
8043
8677
|
// sales/forecast (UPG-579 Option B)
|
|
8044
8678
|
qualification_score: "confidence_5",
|
|
8045
8679
|
// sales/deal — how confident the qualification is (0.24.0)
|
|
8680
|
+
// ── Likelihood ───────────────────────────────────────────────────────────
|
|
8681
|
+
// (0.35.0) Moved off confidence_5. `likelihood` is how probable the event is,
|
|
8682
|
+
// not how well-evidenced the judgment is; the two were sharing one ladder
|
|
8683
|
+
// because no probability ladder existed. Carries market/risk (`risk`,
|
|
8684
|
+
// `market_trend`) and `security.threat.likelihood` in one line.
|
|
8685
|
+
likelihood: "likelihood_5",
|
|
8686
|
+
// compliance/risk, security/threat, market
|
|
8046
8687
|
// ── Effort ───────────────────────────────────────────────────────────────
|
|
8047
8688
|
effort: "effort_5",
|
|
8048
8689
|
// discovery
|
|
@@ -8072,9 +8713,18 @@ var PROPERTY_SCALE_MAP = {
|
|
|
8072
8713
|
// rarity : VRIO distinctiveness; no canonical named scale fits.
|
|
8073
8714
|
// strength : deprecated (validation/evidence; superseded by weight).
|
|
8074
8715
|
};
|
|
8716
|
+
var PROPERTY_SCALE_MAP_BY_ENTITY = {
|
|
8717
|
+
risk: {
|
|
8718
|
+
// Severity of consequences, not magnitude of benefit. See above.
|
|
8719
|
+
impact: "severity_5",
|
|
8720
|
+
// The DEPRECATED spelling of `likelihood`, held on the same ladder as its
|
|
8721
|
+
// replacement for the length of the deprecation window. See above.
|
|
8722
|
+
probability: "likelihood_5"
|
|
8723
|
+
}
|
|
8724
|
+
};
|
|
8075
8725
|
var DEFAULT_SCALE_ID = "scale_5";
|
|
8076
|
-
function getPropertyDefaultScale(
|
|
8077
|
-
return PROPERTY_SCALE_MAP[propertyName] ?? DEFAULT_SCALE_ID;
|
|
8726
|
+
function getPropertyDefaultScale(entityType, propertyName) {
|
|
8727
|
+
return PROPERTY_SCALE_MAP_BY_ENTITY[entityType]?.[propertyName] ?? PROPERTY_SCALE_MAP[propertyName] ?? DEFAULT_SCALE_ID;
|
|
8078
8728
|
}
|
|
8079
8729
|
function getPropertiesForScale(scaleId) {
|
|
8080
8730
|
return Object.entries(PROPERTY_SCALE_MAP).filter(([, id]) => id === scaleId).map(([property]) => property);
|
|
@@ -8165,6 +8815,29 @@ var UPG_ENUM_SCALES = {
|
|
|
8165
8815
|
{ value: "guideline", label: "Guideline", description: "Recommendation: encouraged and expected in most cases, but not enforced." }
|
|
8166
8816
|
]
|
|
8167
8817
|
},
|
|
8818
|
+
// ── EngagementPosture ────────────────────────────────────────────────
|
|
8819
|
+
/**
|
|
8820
|
+
* (0.35.0) The third axis of stakeholder canon, beside the power/interest
|
|
8821
|
+
* grid `influence` / `interest` carry. Those two say how much weight a
|
|
8822
|
+
* stakeholder has and how much they care; neither says which WAY they lean,
|
|
8823
|
+
* and "who blocks this?" is the question a stakeholder map is drawn to answer.
|
|
8824
|
+
*
|
|
8825
|
+
* `tone_direction: 'high-is-good'` reading champion → blocker: the first
|
|
8826
|
+
* value is the desirable end, matching HealthStatus's convention.
|
|
8827
|
+
*/
|
|
8828
|
+
EngagementPosture: {
|
|
8829
|
+
id: "EngagementPosture",
|
|
8830
|
+
label: "Engagement posture",
|
|
8831
|
+
description: "Which way a stakeholder leans on the thing at hand: actively for it, against it, or neither.",
|
|
8832
|
+
tone_direction: "high-is-good",
|
|
8833
|
+
values: [
|
|
8834
|
+
{ value: "champion", label: "Champion", description: "Actively advocates for it and spends their own capital doing so." },
|
|
8835
|
+
{ value: "supporter", label: "Supporter", description: "In favour and will say so when asked, but does not drive it." },
|
|
8836
|
+
{ value: "neutral", label: "Neutral", description: "No position taken; neither helps nor hinders." },
|
|
8837
|
+
{ value: "skeptic", label: "Skeptic", description: "Unconvinced and voices objections, but is not blocking." },
|
|
8838
|
+
{ value: "blocker", label: "Blocker", description: "Actively opposes, and holds enough leverage to stop it." }
|
|
8839
|
+
]
|
|
8840
|
+
},
|
|
8168
8841
|
// ── ProxyConfidence ─────────────────────────────────────────────────
|
|
8169
8842
|
ProxyConfidence: {
|
|
8170
8843
|
id: "ProxyConfidence",
|
|
@@ -8677,6 +9350,62 @@ function getDeprecatedTypes() {
|
|
|
8677
9350
|
return deprecated;
|
|
8678
9351
|
}
|
|
8679
9352
|
var UPG_PROPERTY_MIGRATIONS = {
|
|
9353
|
+
// ── v0.35.0: risk.probability → risk.likelihood, STAGED ─────────────────────
|
|
9354
|
+
//
|
|
9355
|
+
// Deliberately EMPTY, and the emptiness is the design rather than an omission.
|
|
9356
|
+
// The rename is staged: `RiskProperties.probability` is kept, marked
|
|
9357
|
+
// `@deprecated since="0.35.0" removeIn="1.0.0"`, and still read. 0.35.0 changes
|
|
9358
|
+
// what writers emit (`likelihood`) and what readers prefer; it changes no
|
|
9359
|
+
// stored bytes, so there is nothing for a load-time rule to do. A graph
|
|
9360
|
+
// written before 0.35.0 carries `probability` and no `likelihood` and reads
|
|
9361
|
+
// correctly — which is what staging BUYS, and a rule here would spend it.
|
|
9362
|
+
//
|
|
9363
|
+
// Three reasons there is no executable rule, in order of weight:
|
|
9364
|
+
//
|
|
9365
|
+
// 1. A staged deprecation that rewrites data is not staged. The point of the
|
|
9366
|
+
// `epic.estimate` → `effort` pattern this follows is that both names live
|
|
9367
|
+
// through the window and the consumer chooses; migrating the key on load
|
|
9368
|
+
// would end the window on the first read and break any reader still
|
|
9369
|
+
// asking for the old name.
|
|
9370
|
+
// 2. The executable step belongs at the REMOVAL version, not here. That is
|
|
9371
|
+
// the two-step the `removeIn="0.5.0"` properties followed: declared
|
|
9372
|
+
// deprecated with a deadline at 0.4.0, dropped by `drop_props` rules in
|
|
9373
|
+
// the '0.5.0' block below. `probability` is scheduled for the same
|
|
9374
|
+
// treatment at 1.0.0.
|
|
9375
|
+
// 3. No kind fits, and inventing one would be the larger change. Checked
|
|
9376
|
+
// against the APPLIER, not just the type union:
|
|
9377
|
+
// - `drop_props` deletes, which is the 1.0.0 step, not this one.
|
|
9378
|
+
// - `lift_property_to_top_level` targets `UPGBaseNode`, and `likelihood`
|
|
9379
|
+
// is a property, not a top-level field.
|
|
9380
|
+
// - `rename_top_level` renames a top-level field, same mismatch.
|
|
9381
|
+
// - `remap_property_value` is the near miss and still misses twice: it
|
|
9382
|
+
// `break`s unless `typeof oldValue === 'string'` (these values are
|
|
9383
|
+
// `UPGAssessment` objects, or bare numbers in older graphs), and its
|
|
9384
|
+
// `to_property` split OVERWRITES the source with `reset_value`, which
|
|
9385
|
+
// is precisely the staging this release is buying.
|
|
9386
|
+
// - `reshape_value_to_assessment` wraps a bare number on a named scale;
|
|
9387
|
+
// it does not move a key.
|
|
9388
|
+
// The edge-side `kind: 'rename'` (UPG_EDGE_MIGRATIONS, e.g. 0.8.0's
|
|
9389
|
+
// `theme_groups_feature` -> `roadmap_theme_groups_feature`) renames an
|
|
9390
|
+
// EDGE TYPE across instances and has no property-key analogue. Adding one
|
|
9391
|
+
// is new spec surface: a ratified decision, not a side effect of one
|
|
9392
|
+
// property rename.
|
|
9393
|
+
//
|
|
9394
|
+
// The scale moves ride along and need no rule either. `PROPERTY_SCALE_MAP`
|
|
9395
|
+
// resolves `likelihood` to `likelihood_5` (which carries `threat.likelihood`
|
|
9396
|
+
// off `confidence_5` in the same line, since the map is keyed by name) and
|
|
9397
|
+
// `PROPERTY_SCALE_MAP_BY_ENTITY` resolves `risk.impact` to `severity_5`. Both
|
|
9398
|
+
// resolve at READ time, and an assessment only overrides them by PINNING a
|
|
9399
|
+
// `scale_id` on the stored value. Measured across all 36 `.upg` files in the
|
|
9400
|
+
// repo: zero assessments pin a `scale_id` on `likelihood`, `probability` or
|
|
9401
|
+
// `impact`, so there is nothing stamped to re-stamp. One that does pin a
|
|
9402
|
+
// `scale_id` keeps it, by the existing contract.
|
|
9403
|
+
//
|
|
9404
|
+
// What lands at 1.0.0, written down now so it is not rediscovered:
|
|
9405
|
+
// { kind: 'drop_props', type: 'risk', drop_props: ['probability'],
|
|
9406
|
+
// reason: '1.0.0: risk.probability was @deprecated since 0.35.0 with
|
|
9407
|
+
// removeIn="1.0.0"; superseded by `likelihood` on likelihood_5.' }
|
|
9408
|
+
"0.35.0": [],
|
|
8680
9409
|
"0.32.0": [
|
|
8681
9410
|
// C3 — archive becomes a base-node axis. WorkspaceProperties.archived /
|
|
8682
9411
|
// archived_at shipped the orthogonal-archive idea for exactly one type;
|
|
@@ -12254,8 +12983,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12254
12983
|
},
|
|
12255
12984
|
// CompositionProperties
|
|
12256
12985
|
composition: {
|
|
12257
|
-
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
12258
|
-
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
12986
|
+
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement. The clause list is authoritative and the named fields are a positive-only projection of it; since 0.34.0 a clause is a discriminated union on `dimension`, so the `type` axis carries entity types rather than free strings.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
12987
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole: `group_by`, `sort`, `layout`, `nest_by`, and `orphan_disposition` (0.34.0, absent means `'root'`). A consumer may ignore it entirely and still be conformant, because every default it then applies is the safe one.", notes: "THE DESCRIPTION LISTS THE FIELDS ON PURPOSE. This property is `object` in the runtime property registry, so an agent reading `get_entity_schema` gets an opaque blob and this sentence. For an object-typed property the description IS the declared shape, which is why `check:editorial` hashes it (E.4, 0.34.0) and why a field added to `UPGViewPresentation` without a word here would be invisible to every gate and every agent at once." },
|
|
12259
12988
|
members: { type: "object[]", description: "The frozen member arrangement, captured at publish. Layout and pointers only, never resolved content." },
|
|
12260
12989
|
rev: { type: "number", description: "Monotonic revision, bumped on each republish of the same slug.", notes: "A published revision is a fact about the composition, user-visible as a new print from the same plate, so it is serialised. Worth distinguishing from a store's concurrency token, which shares the name in some backends but is a fact about the table rather than about the thing, and is not spec data." },
|
|
12261
12990
|
published_at: { type: "string", description: "ISO timestamp of the most recent publish or republish." },
|
|
@@ -14081,10 +14810,23 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14081
14810
|
// RiskProperties: Risk.
|
|
14082
14811
|
risk: {
|
|
14083
14812
|
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." },
|
|
14813
|
+
likelihood: {
|
|
14814
|
+
type: "assessment",
|
|
14815
|
+
scale_id: "likelihood_5",
|
|
14816
|
+
description: "How likely this risk is to materialise. Rated on `likelihood_5` (Rare \u2192 Almost certain).",
|
|
14817
|
+
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.',
|
|
14818
|
+
properties: {
|
|
14819
|
+
value: { type: "number", description: "The numeric value, used for computation." },
|
|
14820
|
+
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
14821
|
+
scale_id: { type: "string", description: "Which assessment scale this was rated on (optional)." },
|
|
14822
|
+
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
14823
|
+
},
|
|
14824
|
+
required: ["value", "label"]
|
|
14825
|
+
},
|
|
14084
14826
|
probability: {
|
|
14085
14827
|
type: "assessment",
|
|
14086
|
-
scale_id: "
|
|
14087
|
-
description:
|
|
14828
|
+
scale_id: "likelihood_5",
|
|
14829
|
+
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`.',
|
|
14088
14830
|
properties: {
|
|
14089
14831
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
14090
14832
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -14095,8 +14837,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14095
14837
|
},
|
|
14096
14838
|
impact: {
|
|
14097
14839
|
type: "assessment",
|
|
14098
|
-
scale_id: "
|
|
14099
|
-
description: "Severity of consequences if the risk materialises (
|
|
14840
|
+
scale_id: "severity_5",
|
|
14841
|
+
description: "Severity of consequences if the risk materialises. Rated on `severity_5` (Mild inconvenience \u2192 Blocker), NOT the benefit-framed `impact_5`.",
|
|
14842
|
+
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.",
|
|
14100
14843
|
properties: {
|
|
14101
14844
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
14102
14845
|
label: { type: "string", description: "The qualitative label (what the assessor meant)." },
|
|
@@ -14105,7 +14848,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14105
14848
|
},
|
|
14106
14849
|
required: ["value", "label"]
|
|
14107
14850
|
},
|
|
14108
|
-
mitigation: { type: "string", description: "Planned or implemented mitigation strategy" }
|
|
14851
|
+
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." }
|
|
14109
14852
|
},
|
|
14110
14853
|
// RiskRegisterProperties: Risk register.
|
|
14111
14854
|
risk_register: {
|
|
@@ -14359,7 +15102,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14359
15102
|
normalized: { type: "number", description: "Normalized 0-1 value for cross-tool comparison (optional)." }
|
|
14360
15103
|
},
|
|
14361
15104
|
required: ["value", "label"]
|
|
14362
|
-
}
|
|
15105
|
+
},
|
|
15106
|
+
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." },
|
|
15107
|
+
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.' }
|
|
14363
15108
|
},
|
|
14364
15109
|
// StatusReportProperties: Status report.
|
|
14365
15110
|
status_report: {
|
|
@@ -14506,7 +15251,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14506
15251
|
team_type: { type: "string", enum: ["product", "engineering", "design", "growth", "customer_success"], description: "Functional area of the team" },
|
|
14507
15252
|
size: { type: "number", description: "Number of people on the team" },
|
|
14508
15253
|
mission: { type: "string", description: "Team's mission statement" },
|
|
14509
|
-
key_prefix: { type: "string", description: 'Prefix this team mints node keys with (e.g. `"ENTP"`, giving `ENTP-1`, `ENTP-2`, ...). @example "ENTP"', notes: "SUPERSEDES `product.key_prefix` FOR THIS PRODUCT. The moment any team declares a prefix, the
|
|
15254
|
+
key_prefix: { type: "string", description: 'Prefix this team mints node keys with (e.g. `"ENTP"`, giving `ENTP-1`, `ENTP-2`, ...). @example "ENTP"', notes: "SUPERSEDES `product.key_prefix` FOR THIS PRODUCT, AND NOTHING ELSE (normative, narrowed in 0.34.0). The moment any team declares a prefix, the PRODUCT-LEVEL prefix stops being consulted. A product-level prefix that still resolved would win on order alone, and a multi-team product would then silently mint everything under it, which is the defect this field exists to end. THE CANDIDATE SET IS THE UNION OF DECLARED AND OBSERVED PREFIXES. A declaration adds a candidate; it does not remove one. A prefix stops being offered only when something claims it or when nothing has ever minted under it, and while more than one candidate stands the create surface keeps asking. WHY THIS WAS NARROWED, stated because 0.33.0 shipped the wider reading and an implementer built against it. The paragraph above made ANY declaration replace the candidate set outright, and its own stated rationale is entirely about `product.key_prefix`: a SINGLE STRING, declared once, that cannot represent two teams. An OBSERVED prefix is not that. It is evidence of a namespace already in active use. Suppressing it reproduces the precise defect this field exists to end, inside one product, and does so silently. Measured on the only keyed graph in the estate: it carries two observed prefixes, 370 keys under one and 662 under the other, and declares neither. Under the wider reading, one team declaring the smaller prefix collapses the candidate set to it, the picker disappears, and every later create mints under it, including the 662 keys' worth of work belonging to the other namespace. The sentence over-reached beyond its own reason, and this narrows it back to that reason. Shipped as a CHANGE in 0.34.0 rather than as a patch correction. Read as a correction it is defensible, but implementers had already built to the wider text, and moving a contract under them in a patch is how a patch becomes a surprise. A DECLARED PREFIX IS A CANDIDATE BEFORE IT IS OBSERVED. Candidates derived only from keys that already exist cannot see a team's first create, which is the one that most needs asking about. That is why declaration ADDS to the candidate set; it is not a reason for it to subtract. UNIQUENESS IS PRODUCT-SCOPED, AND A TEAM PREFIX DOES NOT WIDEN IT. The key sequence runs per product across entity types, so two teams in one product share one number line and never collide with each other. A team prefix names a team WITHIN that scope; it does not create a sequence that spans products. MINTING IS PRODUCT-SCOPED (normative, 0.33.0). `team` is `portfolio_shared`, so one team node can be referenced from two products. Minting is not portfolio-scoped with it. The rule, and it is a requirement rather than a recommendation: 1. A portfolio-shared team's prefix is NOT a minting candidate in a second product. The first product a team mints under is the only product that prefix mints in. 2. Refusal is NO-KEY. The node is created without a key. Refusal MUST NOT be an exception: a keyless create is a legal outcome everywhere else in the ladder, and throwing here would break creates that succeed today on every surface. Throwing stays reserved for a broken invariant, not for policy. 3. The rule applies on the MINT path, not the picker path, and it covers the INFERRED case. A prefix that was never requested by anyone, and was derived from keys that already exist in the second product, is refused on the same terms as one a caller named. A guard that only inspects an explicitly requested prefix misses the quiet path, and the quiet path is the one a fixture reproduces. WHY THE SCOPE STOPS AT THE PRODUCT. Key uniqueness is enforced by a `(product_id, key)` index, which permits the same key under two product ids by construction. A prefix that minted in two products would therefore run two independent sequences under one name and hand two different nodes the same citation, with nothing objecting. Portfolio-shared team minting is DEFERRED until a supra-product uniqueness design exists, rather than approximated. HOW RULE 1 IS DECIDED: THE EVIDENCE RULE (normative, 0.34.0). Rule 1 names a first product without saying how a minter knows which one it is. It is decided by EVIDENCE, not by a stored marker: at mint, if any other IN-SCOPE product already holds a key under the prefix, refuse. Evidence is derivable from the graphs themselves, needs no migration, cannot go stale, and cannot disagree with the keys. A durable home marker could do all three, and would mint state for a fact the graph already carries. The cost is stated rather than hidden: evidence is SCOPE-DEPENDENT, which is why the scope is ruled below in the same breath rather than left open. THE UNDECIDABLE CASE: NEITHER MINTS (normative, 0.34.0). When two products already hold keys under one prefix and nothing establishes which was first, NEITHER mints. No tiebreak is invented. Creation order is not recorded, and `max(existing)` measures import volume rather than precedence, so any tiebreak would be a guess wearing a rule, and a silent one, since it would attribute a namespace to a product with nothing to say it was wrong. Refusing both is the NO-KEY outcome rule 2 already sanctions, and it is recoverable: once either product declares the prefix, the other is unambiguous and a backfill can run. WHAT \"IN SCOPE\" MEANS: ENGINE-DEFINED, WITH A FLOOR AND A CEILING (normative, 0.34.0). The scope over which the evidence rule looks is defined by the engine, bounded on both sides. FLOOR. It MUST include every product the engine can enumerate for this caller. An engine that looks at fewer products than it can see is choosing not to notice a collision it could have seen. CEILING. It MUST NEVER include a product the caller could not otherwise read. A wider read is a cross-tenant information channel: refusing a mint because of a key in a graph the caller cannot see leaks that the graph exists and what is in it. This half is a security constraint and is not negotiable. PORTFOLIO ALTITUDE IS THE WRONG NORMATIVE ALTITUDE, and it was measured rather than argued: deleting the portfolio document changes nothing about minting, because no minter consults the portfolio seam. Stating the invariant there states it where nobody looks. In practice the local engine's scope is every graph in the workspace folder and the cloud engine's is the caller's own product list. THE HONEST CONSEQUENCE, which belongs in the text rather than in a later surprise: the invariant is SCOPE-RELATIVE. Two engines can legitimately disagree about whether one mint is safe, because they can legitimately see different sets of products. That is a real limitation of the evidence rule and the price of the ceiling. IMMUTABLE ONCE ANYTHING HAS MINTED UNDER IT, PER PRODUCT (normative, 0.34.0). Once any key exists under this prefix IN A GIVEN PRODUCT, the declaration cannot be edited for that product. Refusal-shaped and NO-KEY, matching rule 2: never an exception, because a keyless create is legal everywhere else in the ladder. SCOPED PER PRODUCT, NOT PER TEAM, and the reason is that `team` is `portfolio_shared`: a team that has never minted in product B must still be free to declare there. A per-team global lock would strand it. WHAT IT PREVENTS. Without it, a team edits its prefix after four hundred mints and one product silently carries two number lines under two names, with nothing recording that they were ever one sequence. RENAMING STAYS POSSIBLE BY THE HONEST ROUTE: a migration that rewrites the existing keys. That is a deliberate act with a visible cost, which is the difference between renaming a namespace and forking it by accident. THE PICKER MAY OFFER WHAT THE MINT REFUSES, AND THE SURFACE OWNS THE DIVERGENCE (0.34.0). Rule 3 applies on the MINT path and not the picker path, which is deliberate: the picker cannot cheaply know the answer, since knowing it requires reading other products. The consequence is that a surface can present a choice that then fails. The owner is named here rather than left implicit: A CREATE SURFACE THAT OFFERS A PREFIX THE MINT MAY REFUSE MUST BE ABLE TO REPORT THE REFUSAL, AND MUST BE ABLE TO PRESENT NO-KEY AS AN OUTCOME RATHER THAN AS AN ERROR. This is a design obligation on the surface, not a spec mechanic: the spec cannot fix a UX gap and should not pretend to. The union ruling above shrinks the divergence considerably, because the picker now keeps asking in exactly the case that would otherwise resolve wrongly. This rule states the CONTRACT. The behaviour belongs to whatever mints keys, which is not this package: see `UPGBaseNode.key`." }
|
|
14510
15255
|
},
|
|
14511
15256
|
// TeamOkrProperties: TeamOkr entity.
|
|
14512
15257
|
team_okr: {
|
|
@@ -14617,7 +15362,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14617
15362
|
category: { type: "string", description: 'Attack or threat scenario. @example "injection", "misconfiguration", "social engineering", "supply chain"' },
|
|
14618
15363
|
likelihood: {
|
|
14619
15364
|
type: "assessment",
|
|
14620
|
-
scale_id: "
|
|
15365
|
+
scale_id: "likelihood_5",
|
|
14621
15366
|
description: "Likelihood (1 = theoretical, 5 = actively exploited). @example value 5 for a known, exploitable, commonly targeted pattern",
|
|
14622
15367
|
properties: {
|
|
14623
15368
|
value: { type: "number", description: "The numeric value, used for computation." },
|
|
@@ -14832,8 +15577,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14832
15577
|
},
|
|
14833
15578
|
// WorkspaceProperties: Workspace: a spatial thinking space for arranging entities.
|
|
14834
15579
|
workspace: {
|
|
14835
|
-
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
14836
|
-
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
15580
|
+
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement. The clause list is authoritative and the named fields are a positive-only projection of it; since 0.34.0 a clause is a discriminated union on `dimension`, so the `type` axis carries entity types rather than free strings.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
15581
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole: `group_by`, `sort`, `layout`, `nest_by`, and `orphan_disposition` (0.34.0, absent means `'root'`). A consumer may ignore it entirely and still be conformant, because every default it then applies is the safe one.", notes: "THE DESCRIPTION LISTS THE FIELDS ON PURPOSE. This property is `object` in the runtime property registry, so an agent reading `get_entity_schema` gets an opaque blob and this sentence. For an object-typed property the description IS the declared shape, which is why `check:editorial` hashes it (E.4, 0.34.0) and why a field added to `UPGViewPresentation` without a word here would be invisible to every gate and every agent at once." },
|
|
14837
15582
|
visibility: { type: "string", enum: ["private", "shared", "public"], description: "Who can see this workspace" },
|
|
14838
15583
|
purpose: { type: "string", description: "Free-text description. Pairs with the closed-enum `workspace_purpose`." },
|
|
14839
15584
|
workspace_purpose: { type: "string", enum: ["discovery", "planning", "retrospective", "design", "research", "strategy", "general"], description: "What the workspace is for. Drives template suggestions and surfaces in workspace browsers.", notes: "`discovery` is persona, job and opportunity exploration. `planning` covers roadmap and decision sessions. `retrospective` is reflection on shipped work. `design` is experience or UI exploration. `research` organises study data and synthesis. `strategy` is high-level direction setting. `general` is the catch-all." },
|
|
@@ -21602,10 +22347,16 @@ function validateUPGDocument(doc) {
|
|
|
21602
22347
|
}
|
|
21603
22348
|
const d = doc;
|
|
21604
22349
|
if (!d.upg_version || typeof d.upg_version !== "string") {
|
|
21605
|
-
errors.push({
|
|
22350
|
+
errors.push({
|
|
22351
|
+
path: "$upg.spec_version",
|
|
22352
|
+
message: "spec_version is required and must be a string"
|
|
22353
|
+
});
|
|
21606
22354
|
}
|
|
21607
22355
|
if (!d.exported_at || typeof d.exported_at !== "string") {
|
|
21608
|
-
errors.push({
|
|
22356
|
+
errors.push({
|
|
22357
|
+
path: "$upg.provenance.exported_at",
|
|
22358
|
+
message: "provenance.exported_at is required and must be an ISO 8601 string"
|
|
22359
|
+
});
|
|
21609
22360
|
}
|
|
21610
22361
|
if (!d.source || typeof d.source !== "object") {
|
|
21611
22362
|
errors.push({ path: "$.source", message: "source is required and must be an object" });
|
|
@@ -29301,6 +30052,48 @@ var UPG_ANTI_PATTERNS = [
|
|
|
29301
30052
|
severity: "medium",
|
|
29302
30053
|
source: { kind: "fundamental" }
|
|
29303
30054
|
},
|
|
30055
|
+
// ── Citable-key collision (0.34.0): portfolio-scoped, and the FIRST detector
|
|
30056
|
+
// to ship with a labeled corpus behind it (packages/upg-evals/corpora/
|
|
30057
|
+
// intelligence/duplicate-key-across-products/).
|
|
30058
|
+
//
|
|
30059
|
+
// WHY IT CANNOT BE A SINGLE-GRAPH CHECK. Key uniqueness is enforced by a
|
|
30060
|
+
// `(product_id, key)` index, so each product reports its own key as valid. The
|
|
30061
|
+
// collision is only visible from above, which is why this is `scope: 'portfolio'`
|
|
30062
|
+
// and carries no structured_condition: the detector reads across products, and
|
|
30063
|
+
// IntelligenceCondition composes aggregate counts within one graph.
|
|
30064
|
+
//
|
|
30065
|
+
// MECHANICALLY DECIDABLE, which is unusual in this file and is why it can ship
|
|
30066
|
+
// enforced: one `(prefix, number)` pair appearing in two products of one
|
|
30067
|
+
// portfolio, with no judgement about intent. Every other portfolio entry here
|
|
30068
|
+
// approximates a per-node rule; this one is exact.
|
|
30069
|
+
//
|
|
30070
|
+
// ENFORCED FROM DAY ONE. What was staged is the CORPUS, not the enforcement.
|
|
30071
|
+
// The tempting shape was a check that ships "defined" and becomes "enforced"
|
|
30072
|
+
// later, and there is no such mechanic: a registered detector that declines to
|
|
30073
|
+
// fire is worse than either real option, and nothing in the spec could hang it.
|
|
30074
|
+
// The detector has little to fire on until a second keyed product exists, and
|
|
30075
|
+
// that is a property of the estate rather than of the check.
|
|
30076
|
+
//
|
|
30077
|
+
// THE NEAR-MISSES ARE CONSTRUCTED, and the corpus says so in its own README
|
|
30078
|
+
// rather than in a plan nobody opens. Three classes: two products sharing a
|
|
30079
|
+
// prefix over DISJOINT number ranges (latent, not yet colliding); a
|
|
30080
|
+
// DELIBERATELY shared prefix across a product family wanting one citation
|
|
30081
|
+
// namespace; and a product holding IMPORTED keys minted elsewhere. Until a real
|
|
30082
|
+
// collision is sampled, the recall figure is a claim about the author's
|
|
30083
|
+
// imagination. The corpus carries a dated obligation to re-grade precision and
|
|
30084
|
+
// recall on the first sampled collision.
|
|
30085
|
+
{
|
|
30086
|
+
id: "duplicate-key-across-products",
|
|
30087
|
+
since: "0.34.0",
|
|
30088
|
+
scope: "portfolio",
|
|
30089
|
+
name: "One citable key, two products",
|
|
30090
|
+
description: "The same (prefix, number) pair identifies a node in two or more products of one portfolio. Keys are minted per product and uniqueness is enforced by a (product_id, key) index, so two products minting under one prefix run two independent sequences under one name. Each product reports its own key as valid and only the portfolio can see that the citation is ambiguous.",
|
|
30091
|
+
why_it_matters: "A key exists to be cited. When one citation resolves to two different things, every reference made with it becomes ambiguous after the fact, including references already written down outside the graph where nothing can be corrected. The damage is retroactive and grows with adoption: the longer both sequences run, the more citations a reconciliation has to rewrite.",
|
|
30092
|
+
remediation: "Decide which product owns the prefix and declare it there with team.key_prefix, so ownership is explicit rather than inferred. Migrate the other product by rewriting its existing keys, which is deliberate and visibly costly, never by editing a declaration under a live sequence. If both products want one namespace, record that decision so it does not read as drift.",
|
|
30093
|
+
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
30094
|
+
severity: "high",
|
|
30095
|
+
source: { kind: "fundamental" }
|
|
30096
|
+
},
|
|
29304
30097
|
// ── surface (0.27.0): the place, its guest list, and its arbitration rule ──
|
|
29305
30098
|
// Three patterns, calibrated so the contention one leads and the two coverage
|
|
29306
30099
|
// companions sit a tier below it. Like every entry in this file, each detector
|
|
@@ -33045,8 +33838,8 @@ var NODE_KEY_ORDER = [
|
|
|
33045
33838
|
"sort_order",
|
|
33046
33839
|
"properties"
|
|
33047
33840
|
];
|
|
33048
|
-
var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "properties"];
|
|
33049
|
-
var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence", "alias", "relevance", "audience_role"];
|
|
33841
|
+
var EDGE_KEY_ORDER = ["id", "source", "target", "type", "mapping_confidence", "provenance", "properties"];
|
|
33842
|
+
var CROSS_EDGE_KEY_ORDER = ["id", "source", "target", "type", "source_product_id", "target_product_id", "mapping_confidence", "properties", "alias", "relevance", "audience_role"];
|
|
33050
33843
|
var PRODUCT_KEY_ORDER = ["id", "title", "description", "stage", "properties"];
|
|
33051
33844
|
function canonicalNode(node) {
|
|
33052
33845
|
const repaired = repairNodeDrift(node);
|
|
@@ -33063,7 +33856,14 @@ function canonicalEdge(edge) {
|
|
|
33063
33856
|
}
|
|
33064
33857
|
function canonicalCrossEdge(edge) {
|
|
33065
33858
|
return orderedObject(edge, CROSS_EDGE_KEY_ORDER, {
|
|
33066
|
-
forceKeys: ["id", "source", "target", "type"]
|
|
33859
|
+
forceKeys: ["id", "source", "target", "type"],
|
|
33860
|
+
// `properties` is an OPEN bag and must be canonicalised like every other one
|
|
33861
|
+
// (node, edge, product all pass it). Without this, two writers emitting the
|
|
33862
|
+
// same parity assessment with their keys in a different order produce
|
|
33863
|
+
// different bytes, which breaks contract 3 of this format: input key order
|
|
33864
|
+
// does not change the output. Free to fix today and measured as such: zero
|
|
33865
|
+
// of the 61 cross-edges in the estate carry properties.
|
|
33866
|
+
openKeys: ["properties"]
|
|
33067
33867
|
});
|
|
33068
33868
|
}
|
|
33069
33869
|
function canonicalProduct(product) {
|
|
@@ -33324,7 +34124,7 @@ function checkHeaderSealText(text) {
|
|
|
33324
34124
|
}
|
|
33325
34125
|
|
|
33326
34126
|
// src/index.ts
|
|
33327
|
-
var UPG_VERSION = "0.
|
|
34127
|
+
var UPG_VERSION = "0.35.0";
|
|
33328
34128
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
33329
34129
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
33330
34130
|
var UPG_TYPES = getTypes();
|
|
@@ -33387,6 +34187,14 @@ var UPG_ENTITY_COUNT = UPG_TYPES.length;
|
|
|
33387
34187
|
var UPG_DOMAIN_COUNT = UPG_DOMAINS.length;
|
|
33388
34188
|
var UPG_EDGE_COUNT = UPG_EDGE_TYPES.length;
|
|
33389
34189
|
var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
34190
|
+
var UPG_PHASE_COUNT = (() => {
|
|
34191
|
+
const pairs = /* @__PURE__ */ new Set();
|
|
34192
|
+
for (const lifecycle of UPG_LIFECYCLES) {
|
|
34193
|
+
const owner = lifecycle.template_id ? `T:${lifecycle.template_id}` : `E:${lifecycle.entity_type}`;
|
|
34194
|
+
for (const phase of lifecycle.phases) pairs.add(`${owner}::${phase.id}`);
|
|
34195
|
+
}
|
|
34196
|
+
return pairs.size;
|
|
34197
|
+
})();
|
|
33390
34198
|
// Annotate the CommonJS export names for ESM import in node:
|
|
33391
34199
|
0 && (module.exports = {
|
|
33392
34200
|
ACTIVE_WHEN_PROPERTY,
|
|
@@ -33399,10 +34207,12 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33399
34207
|
CONFIGURATION_VARIANCE_EDGE_PROPERTY_SCHEMA,
|
|
33400
34208
|
CONTENT_DEPTH_WARNING_RULES,
|
|
33401
34209
|
COVERAGE_ANTI_PATTERNS,
|
|
34210
|
+
CROSS_EDGE_KEY_ORDER,
|
|
33402
34211
|
DEFAULT_ENTITY_EMOJI,
|
|
33403
34212
|
DEFERRED_PROPOSED_BY_CONTRACT,
|
|
33404
34213
|
DEFER_EDGE_PROPERTY_SCHEMA,
|
|
33405
34214
|
DISCOVERY_RESEARCH_VALIDATION_PLAYBOOK,
|
|
34215
|
+
EDGE_KEY_ORDER,
|
|
33406
34216
|
ENGINEERING_PLATFORM_PLAYBOOK,
|
|
33407
34217
|
ENTITY_EMOJI,
|
|
33408
34218
|
EXPERIENCE_DESIGN_BRAND_PLAYBOOK,
|
|
@@ -33417,6 +34227,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33417
34227
|
PROPERTY_MODIFIERS,
|
|
33418
34228
|
PROPERTY_MODIFIER_SEMANTICS,
|
|
33419
34229
|
PROPERTY_SCALE_MAP,
|
|
34230
|
+
PROPERTY_SCALE_MAP_BY_ENTITY,
|
|
33420
34231
|
QUALIFIABLE_EDGE_TYPES,
|
|
33421
34232
|
RECORD_ENTITY_TYPES,
|
|
33422
34233
|
REFLECT_MODES,
|
|
@@ -33453,6 +34264,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33453
34264
|
UPG_EDGE_PAIR_MAP,
|
|
33454
34265
|
UPG_EDGE_TYPES,
|
|
33455
34266
|
UPG_ENTITY_COUNT,
|
|
34267
|
+
UPG_ENTITY_DESCRIPTIONS,
|
|
33456
34268
|
UPG_ENTITY_FILTER_SPECS,
|
|
33457
34269
|
UPG_ENTITY_META,
|
|
33458
34270
|
UPG_ENTITY_META_BY_ID,
|
|
@@ -33470,7 +34282,9 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33470
34282
|
UPG_LIFECYCLE_PLANNED_TYPES,
|
|
33471
34283
|
UPG_META_COUNT,
|
|
33472
34284
|
UPG_MIGRATIONS,
|
|
34285
|
+
UPG_PHASE_COUNT,
|
|
33473
34286
|
UPG_PLAYBOOKS,
|
|
34287
|
+
UPG_POLYMORPHIC_EDGE_FAMILIES,
|
|
33474
34288
|
UPG_POLYMORPHIC_EDGE_KEYS,
|
|
33475
34289
|
UPG_PORTFOLIO_KINDS,
|
|
33476
34290
|
UPG_PORTFOLIO_SHARED_TYPES,
|
|
@@ -33552,6 +34366,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33552
34366
|
getDomainIdForType,
|
|
33553
34367
|
getDomainsInRing,
|
|
33554
34368
|
getEdgePropertySchema,
|
|
34369
|
+
getEntityDescription,
|
|
33555
34370
|
getEntityModifierSummary,
|
|
33556
34371
|
getEnumScale,
|
|
33557
34372
|
getEnumValueMeta,
|