@unified-product-graph/core 0.31.0 → 0.32.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 +37 -1
- package/README.md +1 -1
- package/dist/index.cjs +235 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11094 -10645
- package/dist/index.d.ts +11094 -10645
- package/dist/index.js +235 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -775,7 +775,7 @@ var UPG_DOMAINS = [
|
|
|
775
775
|
id: "workspace",
|
|
776
776
|
label: "Workspace",
|
|
777
777
|
description: "Spatial thinking spaces for arranging entities, debating decisions, and committing to the graph. Workspaces are transient canvases that sit alongside all other domains, letting you compose and explore relationships before they become permanent graph structure. A framework exercise is a structured workspace: one run of a framework (MoSCoW, RICE, Kano, \u2026) applied to a chosen set of entities, with each entity's result recorded on the exercise-to-entity edge rather than the entity itself. A composition is the durable other half: a named, published view assembled from a canvas, with a stable slug people link to and a revision history, whose frozen arrangement holds pointers rather than copied content.",
|
|
778
|
-
types: ["workspace", "framework_exercise", "composition"]
|
|
778
|
+
types: ["workspace", "framework_exercise", "composition", "capture"]
|
|
779
779
|
},
|
|
780
780
|
{
|
|
781
781
|
id: "foundations",
|
|
@@ -1263,7 +1263,10 @@ var UPG_ENTITY_META = [
|
|
|
1263
1263
|
// ── Workspace ──
|
|
1264
1264
|
{ name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
|
|
1265
1265
|
{ name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" },
|
|
1266
|
-
{ name: "composition", type_id: "ent_361", maturity: "proposed", since: "0.31.0" }
|
|
1266
|
+
{ name: "composition", type_id: "ent_361", maturity: "proposed", since: "0.31.0" },
|
|
1267
|
+
// A dated, hashed rendition of a node that is already in the graph. Enters
|
|
1268
|
+
// `proposed` per the maturity rubric (precedent: composition 0.31.0).
|
|
1269
|
+
{ name: "capture", type_id: "ent_362", maturity: "proposed", since: "0.32.0" }
|
|
1267
1270
|
];
|
|
1268
1271
|
var DEFERRED_PROPOSED_BY_CONTRACT = {
|
|
1269
1272
|
// user_story (UPG-571) and experiment_run (UPG-664) graduated to stable in
|
|
@@ -1551,6 +1554,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
1551
1554
|
// the catalog's singular-source/target grammar (polymorphic targets
|
|
1552
1555
|
// would be a separate catalog-mechanic change).
|
|
1553
1556
|
competitive_analysis_dimensioned_by_classification_axis: { forward_verb: "dimensioned_by", reverse_verb: "dimensions", classification: "hierarchy", source_type: "competitive_analysis", target_type: "classification_axis" },
|
|
1557
|
+
// A product's own classification axes (0.32.0). Before this the only parent a
|
|
1558
|
+
// classification_axis could have was a competitive_analysis, so a taxonomy the
|
|
1559
|
+
// product uses on its OWN entities — the grouped-label case, one named group
|
|
1560
|
+
// over a set of values — had to hang off a competitive analysis it has nothing
|
|
1561
|
+
// to do with, or float parentless.
|
|
1562
|
+
//
|
|
1563
|
+
// Hierarchy, so the axis has a home in get_tree, on the same honest verb the
|
|
1564
|
+
// competitive_analysis edge already uses; and it earns its
|
|
1565
|
+
// UPG_VALID_CHILDREN.product pair the same way product_defines_configuration_axis
|
|
1566
|
+
// did at 0.30.0 — axes are few per product, and visibility of a mechanism
|
|
1567
|
+
// matters more than the tree cost.
|
|
1568
|
+
//
|
|
1569
|
+
// This is what makes one level of label grouping expressible with NO new label
|
|
1570
|
+
// surface: the group is the axis, the labels are its values, and carrying one
|
|
1571
|
+
// is node_classified_as_classification_value, all of it stable since 0.4.0.
|
|
1572
|
+
// Ungrouped freeform labels stay in `tags`. The line between them: if the
|
|
1573
|
+
// group has a name a person would filter by, it is an axis; otherwise a tag.
|
|
1574
|
+
product_dimensioned_by_classification_axis: { forward_verb: "dimensioned_by", reverse_verb: "dimensions", classification: "hierarchy", source_type: "product", target_type: "classification_axis" },
|
|
1554
1575
|
classification_axis_includes_classification_value: { forward_verb: "includes", reverse_verb: "value_of", classification: "hierarchy", source_type: "classification_axis", target_type: "classification_value" },
|
|
1555
1576
|
// 0.10.2: also dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so a
|
|
1556
1577
|
// competitor can be classified directly against a `registry/{classification_value}`
|
|
@@ -1790,10 +1811,20 @@ var UPG_EDGE_CATALOG = {
|
|
|
1790
1811
|
// iterations; a cycle contains its cooldown), mirroring team_contains_team.
|
|
1791
1812
|
planning_cycle_contains_planning_cycle: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "planning_cycle", target_type: "planning_cycle" },
|
|
1792
1813
|
// E3: scheduling work into a cycle is a deliberate authoring act, not a
|
|
1793
|
-
// containment nesting —
|
|
1794
|
-
// referenced by the cycle. deliberate_only so the generic-inference
|
|
1795
|
-
// never auto-materialise it.
|
|
1796
|
-
|
|
1814
|
+
// containment nesting — the work item keeps its feature/epic parent and is
|
|
1815
|
+
// merely referenced by the cycle. deliberate_only so the generic-inference
|
|
1816
|
+
// chokepoints never auto-materialise it.
|
|
1817
|
+
//
|
|
1818
|
+
// WIDENED AT 0.32.0, renamed from planning_cycle_schedules_user_story. The
|
|
1819
|
+
// story-only endpoint could not hold a real tracker import: the adapters
|
|
1820
|
+
// default an unrecognised issue to `task`, so the type a cycle most needed to
|
|
1821
|
+
// schedule was the one type it could not reach. Endpoint-polymorphic over the
|
|
1822
|
+
// work-item set {feature, epic, user_story, task, bug} by the same
|
|
1823
|
+
// construction as work_item_blocks_work_item in the same 0.20.0 batch — the
|
|
1824
|
+
// `work_item` token names the intended semantic domain, the endpoint is the
|
|
1825
|
+
// `node` wildcard. Three typed edges were the alternative and would have
|
|
1826
|
+
// re-opened a decision this vocabulary already took for the same entity set.
|
|
1827
|
+
planning_cycle_schedules_work_item: { forward_verb: "schedules", reverse_verb: "scheduled_in", classification: "semantic", source_type: "planning_cycle", target_type: "node", deliberate_only: true },
|
|
1797
1828
|
// E2: the OKR cycle-scoping anchor. objective -> planning_cycle. planning_cycle
|
|
1798
1829
|
// is portfolio_shared, so this passes the cross-scope gate and ships as
|
|
1799
1830
|
// PROVISIONAL (allowed cross-product with a write-time warning, self-maintaining,
|
|
@@ -2918,6 +2949,16 @@ var UPG_EDGE_CATALOG = {
|
|
|
2918
2949
|
// NOT cross_product_eligible: a portfolio-level composition spanning several
|
|
2919
2950
|
// product graphs is conceivable but unevidenced. Same YAGNI call as above.
|
|
2920
2951
|
composition_focuses_node: { forward_verb: "focuses", reverse_verb: "focused_in", classification: "cross-domain", source_type: "composition", target_type: "node" },
|
|
2952
|
+
// What a capture is a picture OF (0.32.0). Polymorphic because anything in the
|
|
2953
|
+
// graph can be rendered — a surface, a screen, a report, a canvas — and being
|
|
2954
|
+
// captured carries no structural role, so the endpoint collapses to the
|
|
2955
|
+
// wildcard rather than enumerating a list that would never finish.
|
|
2956
|
+
//
|
|
2957
|
+
// This edge is also where the preservation-is-not-permission rule lands for
|
|
2958
|
+
// renditions: a consumer renders the captures the GRAPH says exist, never the
|
|
2959
|
+
// ones some tool's opaque bag still remembers. A capture that was deleted is a
|
|
2960
|
+
// node that was deleted, and the spec needs no second deletion vocabulary.
|
|
2961
|
+
capture_renders_node: { forward_verb: "renders", reverse_verb: "rendered_by", classification: "cross-domain", source_type: "capture", target_type: "node" },
|
|
2921
2962
|
// ── P14 edges (replacing foreign-key properties) ─────────────────────────
|
|
2922
2963
|
feature_addresses_job: { forward_verb: "addresses", reverse_verb: "addressed_by", classification: "cross-domain", source_type: "feature", target_type: "job" },
|
|
2923
2964
|
feature_drives_key_result: { forward_verb: "drives", reverse_verb: "driven_by", classification: "cross-domain", source_type: "feature", target_type: "key_result" },
|
|
@@ -3034,6 +3075,23 @@ var UPG_EDGE_CATALOG = {
|
|
|
3034
3075
|
node_owned_by_stakeholder: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "stakeholder" },
|
|
3035
3076
|
node_owned_by_department: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "department", cross_product_eligible: true },
|
|
3036
3077
|
node_owned_by_person: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "person" },
|
|
3078
|
+
// ── Cross-domain: Assignment (0.32.0) ───────────────────────────────────────
|
|
3079
|
+
// Assignment is NOT ownership, and 0.12.0 ruled otherwise. That ruling routed
|
|
3080
|
+
// task.assignee and bug.assignee into node_owned_by_person, and it is
|
|
3081
|
+
// superseded here rather than quietly widened, because the two differ in a way
|
|
3082
|
+
// the field measures.
|
|
3083
|
+
//
|
|
3084
|
+
// ASSIGNMENT HAS A TIME INTERVAL AND OWNERSHIP DOES NOT. A shipped ops plane
|
|
3085
|
+
// models assignment as a claim carrying claimed-at, released-at and a partial
|
|
3086
|
+
// unique index enforcing one live claim; nothing in the spec gives ownership
|
|
3087
|
+
// that shape. And a real 184-item board ran with 157 items (85%) unassigned
|
|
3088
|
+
// while every one of them was owned in the ordinary sense. One edge meaning
|
|
3089
|
+
// both cannot express "owned by the team, assigned to nobody", which is the
|
|
3090
|
+
// modal state of a working board.
|
|
3091
|
+
//
|
|
3092
|
+
// Not cross_product_eligible, matching node_owned_by_person: an assignee is a
|
|
3093
|
+
// person in this graph's own roster.
|
|
3094
|
+
node_assigned_to_person: { forward_verb: "assigned_to", reverse_verb: "assigned", classification: "cross-domain", source_type: "node", target_type: "person" },
|
|
3037
3095
|
// ── Cross-domain: Architecture (DDD) ─────────────────────────────────────────
|
|
3038
3096
|
// Service, domain_event, domain_entity, aggregate, read_model, api_contract,
|
|
3039
3097
|
// value_object, command, and data_model are DDD building blocks that all
|
|
@@ -3978,7 +4036,18 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
|
3978
4036
|
// onto a canvas or shown in a published view, and neither carries a
|
|
3979
4037
|
// structural role, so both collapse polymorphic rather than enumerating.
|
|
3980
4038
|
"workspace_arranges_node",
|
|
3981
|
-
"composition_focuses_node"
|
|
4039
|
+
"composition_focuses_node",
|
|
4040
|
+
// Universal assignment (0.32.0): who is working on this, as distinct from who
|
|
4041
|
+
// owns it. Wildcard source for the same reason node_owned_by_person has one.
|
|
4042
|
+
"node_assigned_to_person",
|
|
4043
|
+
// Capture subject (0.32.0): anything in the graph can be rendered, and being
|
|
4044
|
+
// rendered carries no structural role.
|
|
4045
|
+
"capture_renders_node",
|
|
4046
|
+
// Cadence scheduling (0.32.0, widened from planning_cycle_schedules_user_story):
|
|
4047
|
+
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
4048
|
+
// bug}. The `work_item` key token names the intended semantic domain; the
|
|
4049
|
+
// endpoint is the `node` wildcard.
|
|
4050
|
+
"planning_cycle_schedules_work_item"
|
|
3982
4051
|
];
|
|
3983
4052
|
var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
|
|
3984
4053
|
function isPolymorphicEdge(key) {
|
|
@@ -4173,6 +4242,11 @@ var UPG_VALID_CHILDREN = {
|
|
|
4173
4242
|
// the product renders. Few per product, and top-level because an axis is a
|
|
4174
4243
|
// product-wide fact rather than a child of any one feature or surface.
|
|
4175
4244
|
"configuration_axis",
|
|
4245
|
+
// Classification (0.32.0): the product's own taxonomies — the grouped-label
|
|
4246
|
+
// case, one named group over a set of values. Same reasoning as the
|
|
4247
|
+
// configuration axis above, and until now the only parent a
|
|
4248
|
+
// classification_axis could have was a competitive_analysis.
|
|
4249
|
+
"classification_axis",
|
|
4176
4250
|
// Engineering
|
|
4177
4251
|
"bounded_context",
|
|
4178
4252
|
"code_repository",
|
|
@@ -4457,7 +4531,7 @@ var UPG_VALID_CHILDREN = {
|
|
|
4457
4531
|
// contains iterations; a cycle contains its cooldown) via
|
|
4458
4532
|
// planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
|
|
4459
4533
|
// child: scheduling work into a cycle is the semantic, deliberate-only
|
|
4460
|
-
//
|
|
4534
|
+
// planning_cycle_schedules_work_item edge, so the item keeps its feature/epic
|
|
4461
4535
|
// containment parent and is merely referenced by the cycle.
|
|
4462
4536
|
planning_cycle: ["planning_cycle"],
|
|
4463
4537
|
// ── Engineering hierarchy ───────────────────────────────────────────────────
|
|
@@ -4803,7 +4877,12 @@ var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
4803
4877
|
// part of the product, it is a lens onto one. This is also what keeps the
|
|
4804
4878
|
// type to two edges rather than three: no provenance edge is minted (that is
|
|
4805
4879
|
// `workspace_produced_node`, reused) and no parent edge is needed.
|
|
4806
|
-
"composition"
|
|
4880
|
+
"composition",
|
|
4881
|
+
// A capture has no structural parent for the same reason: it anchors to what
|
|
4882
|
+
// it RENDERS, via `capture_renders_node`. Giving it a product parent would
|
|
4883
|
+
// claim a capture is part of the product rather than a picture of one, and
|
|
4884
|
+
// would mint a containment edge whose only job is to satisfy the hierarchy.
|
|
4885
|
+
"capture"
|
|
4807
4886
|
]);
|
|
4808
4887
|
function isContainmentFreeType(entityType) {
|
|
4809
4888
|
return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
|
|
@@ -6909,7 +6988,8 @@ var WORK_ITEM_TEMPLATE = {
|
|
|
6909
6988
|
initial_phase: "todo",
|
|
6910
6989
|
terminal_phases: ["done", "cancelled"],
|
|
6911
6990
|
phases: [
|
|
6912
|
-
{ id: "
|
|
6991
|
+
{ id: "backlog", status_category: "backlog", label: "Backlog", description: "Accepted or set aside deliberately: real work, not scheduled. Distinct from `todo`, which is committed and merely not started.", transitions_to: ["todo", "in_progress", "cancelled"] },
|
|
6992
|
+
{ id: "todo", status_category: "unstarted", label: "To Do", description: "Identified but not yet started.", transitions_to: ["in_progress", "backlog", "cancelled"] },
|
|
6913
6993
|
{ id: "in_progress", status_category: "started", label: "In Progress", description: "Actively being worked on.", transitions_to: ["in_review", "todo", "cancelled"] },
|
|
6914
6994
|
{ id: "in_review", status_category: "started", label: "In Review", description: "Work completed, awaiting review or acceptance.", transitions_to: ["done", "in_progress", "cancelled"] },
|
|
6915
6995
|
{ id: "done", status_category: "completed", label: "Done", description: "Completed and accepted.", transitions_to: [] },
|
|
@@ -7540,7 +7620,11 @@ var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
7540
7620
|
// from experiment_runs (which have lifecycles); hypothesis_evidence was
|
|
7541
7621
|
// lifecycle-free here but is deprecated at v0.4.0 ──────────────────────────────────────
|
|
7542
7622
|
"evidence",
|
|
7543
|
-
"learning"
|
|
7623
|
+
"learning",
|
|
7624
|
+
// ── Workspace (1 of 4): a capture is a fact about a moment, not a workflow.
|
|
7625
|
+
// It is taken, and from then on it either still matches its subject or it
|
|
7626
|
+
// does not — which is what `content_hash` answers, not a phase. ─────────
|
|
7627
|
+
"capture"
|
|
7544
7628
|
]);
|
|
7545
7629
|
function isLifecycleFreeType(entityType) {
|
|
7546
7630
|
return UPG_LIFECYCLE_FREE_TYPES.has(entityType);
|
|
@@ -8444,6 +8528,46 @@ function getDeprecatedTypes() {
|
|
|
8444
8528
|
return deprecated;
|
|
8445
8529
|
}
|
|
8446
8530
|
var UPG_PROPERTY_MIGRATIONS = {
|
|
8531
|
+
"0.32.0": [
|
|
8532
|
+
// C3 — archive becomes a base-node axis. WorkspaceProperties.archived /
|
|
8533
|
+
// archived_at shipped the orthogonal-archive idea for exactly one type;
|
|
8534
|
+
// field data showed it is general (a tracker held 559 archived-completed
|
|
8535
|
+
// items beside 18 live-completed ones), so the pair moves to UPGBaseNode
|
|
8536
|
+
// and the workspace properties are @deprecated. Lift, not drop: the value
|
|
8537
|
+
// is real and its new home means the same thing.
|
|
8538
|
+
{
|
|
8539
|
+
kind: "lift_property_to_top_level",
|
|
8540
|
+
type: "workspace",
|
|
8541
|
+
from_property: "archived",
|
|
8542
|
+
to: "archived",
|
|
8543
|
+
reason: "0.32.0: archive generalised from WorkspaceProperties to UPGBaseNode.archived. Same fact, wider home; the workspace property is @deprecated."
|
|
8544
|
+
},
|
|
8545
|
+
{
|
|
8546
|
+
kind: "lift_property_to_top_level",
|
|
8547
|
+
type: "workspace",
|
|
8548
|
+
from_property: "archived_at",
|
|
8549
|
+
to: "archived_at",
|
|
8550
|
+
reason: "0.32.0: pairs with the archived lift above. UPGBaseNode.archived_at is the canonical home."
|
|
8551
|
+
},
|
|
8552
|
+
// C4 — the duplicate label surfaces. TaskProperties.labels and
|
|
8553
|
+
// BugProperties.labels were declared "applied uniformly across work item
|
|
8554
|
+
// types" and duplicated base-node `tags` with no consumer anywhere. Three
|
|
8555
|
+
// label surfaces (base tags, per-type tags, per-type labels) is two too
|
|
8556
|
+
// many; grouped labels are classification_axis + classification_value, and
|
|
8557
|
+
// ungrouped ones are `tags`.
|
|
8558
|
+
{
|
|
8559
|
+
kind: "drop_props",
|
|
8560
|
+
type: "task",
|
|
8561
|
+
drop_props: ["labels"],
|
|
8562
|
+
reason: "0.32.0: TaskProperties.labels duplicated base-node `tags` and had no consumers. Freeform labels live in `tags`; grouped labels are classification_axis + node_classified_as_classification_value."
|
|
8563
|
+
},
|
|
8564
|
+
{
|
|
8565
|
+
kind: "drop_props",
|
|
8566
|
+
type: "bug",
|
|
8567
|
+
drop_props: ["labels"],
|
|
8568
|
+
reason: "0.32.0: BugProperties.labels duplicated base-node `tags` and had no consumers. Same resolution as task."
|
|
8569
|
+
}
|
|
8570
|
+
],
|
|
8447
8571
|
// ── v0.21.0: UPG-690 D.1 — collapse residual *_status shadows into status ────
|
|
8448
8572
|
//
|
|
8449
8573
|
// Six `*_status` enum properties that re-encode their entity's own lifecycle
|
|
@@ -9579,6 +9703,48 @@ function getSplitMigrations(fromVersion, toVersion) {
|
|
|
9579
9703
|
return result;
|
|
9580
9704
|
}
|
|
9581
9705
|
var UPG_SCALAR_TO_EDGE_MIGRATIONS = {
|
|
9706
|
+
"0.32.0": [
|
|
9707
|
+
// C2 — assignment gets its own edge, superseding the 0.12.0 rules that
|
|
9708
|
+
// routed task/bug `assignee` into node_owned_by_person.
|
|
9709
|
+
//
|
|
9710
|
+
// WHY THIS IS A REVERSAL AND NOT A GAP-FILL. 0.12.0 ruled that an assignee
|
|
9711
|
+
// is an owner. Assignment has a time interval and an exclusivity that
|
|
9712
|
+
// ownership does not, and a real board ran 85% unassigned while every item
|
|
9713
|
+
// was owned — a state one edge cannot express.
|
|
9714
|
+
//
|
|
9715
|
+
// WHY EXISTING EDGES ARE NOT REWRITTEN. A node_owned_by_person edge on a
|
|
9716
|
+
// task is indistinguishable by inspection from a genuine ownership edge, so
|
|
9717
|
+
// a blanket rename would silently reclassify real facts. Measured population
|
|
9718
|
+
// across every graph in the originating workspace was ~zero, which makes the
|
|
9719
|
+
// cost of leaving them nil and the cost of guessing a wrong fact.
|
|
9720
|
+
//
|
|
9721
|
+
// user_story is included here and was MISSING from 0.12.0 entirely, though
|
|
9722
|
+
// it has carried `assignee` since 0.20.0.
|
|
9723
|
+
{
|
|
9724
|
+
from_type: "task",
|
|
9725
|
+
scalar_property: "assignee",
|
|
9726
|
+
target_type: "person",
|
|
9727
|
+
edge_type: "node_assigned_to_person",
|
|
9728
|
+
drop_scalar: false,
|
|
9729
|
+
reason: 'P14 actor: the assignee is a first-class person ("every task assigned to this person"). Assignment is not ownership: it has an interval and an exclusivity that ownership lacks, so it links node_assigned_to_person, superseding the 0.12.0 node_owned_by_person rule. Display string kept.'
|
|
9730
|
+
},
|
|
9731
|
+
{
|
|
9732
|
+
from_type: "bug",
|
|
9733
|
+
scalar_property: "assignee",
|
|
9734
|
+
target_type: "person",
|
|
9735
|
+
edge_type: "node_assigned_to_person",
|
|
9736
|
+
drop_scalar: false,
|
|
9737
|
+
reason: "P14 actor: same as task. Supersedes the 0.12.0 node_owned_by_person rule. Display string kept."
|
|
9738
|
+
},
|
|
9739
|
+
{
|
|
9740
|
+
from_type: "user_story",
|
|
9741
|
+
scalar_property: "assignee",
|
|
9742
|
+
target_type: "person",
|
|
9743
|
+
edge_type: "node_assigned_to_person",
|
|
9744
|
+
drop_scalar: false,
|
|
9745
|
+
reason: "P14 actor: user_story has carried `assignee` since 0.20.0 and had no promotion rule at all; an asymmetry with task and bug that this closes. Display string kept."
|
|
9746
|
+
}
|
|
9747
|
+
],
|
|
9582
9748
|
"0.12.0": [
|
|
9583
9749
|
// ── Bucket A1 — orphan scalars (no edge existed): add edge + migrate + drop ──
|
|
9584
9750
|
{
|
|
@@ -9989,22 +10155,12 @@ var UPG_SCALAR_TO_EDGE_MIGRATIONS = {
|
|
|
9989
10155
|
drop_scalar: false,
|
|
9990
10156
|
reason: "P14 actor: the release owner is a first-class person; link node_owned_by_person. Display string kept."
|
|
9991
10157
|
},
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
reason: 'P14 actor: the assignee is a first-class person ("every task assigned to this person"); link node_owned_by_person. Display string kept.'
|
|
9999
|
-
},
|
|
10000
|
-
{
|
|
10001
|
-
from_type: "bug",
|
|
10002
|
-
scalar_property: "assignee",
|
|
10003
|
-
target_type: "person",
|
|
10004
|
-
edge_type: "node_owned_by_person",
|
|
10005
|
-
drop_scalar: false,
|
|
10006
|
-
reason: "P14 actor: the assignee is a first-class person; link node_owned_by_person. Display string kept."
|
|
10007
|
-
},
|
|
10158
|
+
// NOTE (0.32.0): the task/bug `assignee` rules that lived here are SUPERSEDED
|
|
10159
|
+
// by the '0.32.0' block below, which routes them to node_assigned_to_person.
|
|
10160
|
+
// They are removed rather than left to double-run: two rules over one scalar
|
|
10161
|
+
// would mint the person once and link it twice, under two verbs that now mean
|
|
10162
|
+
// different things. Graphs promoted before 0.32.0 keep the ownership edges
|
|
10163
|
+
// they were given — see the 0.32.0 block for why those are not rewritten.
|
|
10008
10164
|
{
|
|
10009
10165
|
from_type: "roadmap",
|
|
10010
10166
|
scalar_property: "owner",
|
|
@@ -10100,6 +10256,14 @@ function getScalarToEdgeMigrations(fromVersion, toVersion) {
|
|
|
10100
10256
|
return result;
|
|
10101
10257
|
}
|
|
10102
10258
|
var UPG_EDGE_MIGRATIONS = {
|
|
10259
|
+
"0.32.0": [
|
|
10260
|
+
// C5 — the cadence scheduling edge widens from user_story to the work-item
|
|
10261
|
+
// set. No requires_target_type: the destination's target_type is the `node`
|
|
10262
|
+
// wildcard, and endpoint guards are checked against the catalog's declared
|
|
10263
|
+
// type rather than the concrete instance (same reasoning as the 0.22.0
|
|
10264
|
+
// workspace_produced_node rename below). Gate only on the unwidened source.
|
|
10265
|
+
{ kind: "rename", from: "planning_cycle_schedules_user_story", to: "planning_cycle_schedules_work_item", requires_source_type: "planning_cycle", reason: "0.32.0: a cycle could only schedule a user_story, while tracker imports produce `task` by default; the type a cadence most needed to hold was the one it could not reach. Widened to the polymorphic work-item endpoint; no flip, source unchanged. Measured live population of the old type at release: zero (no .upg in the repo or the dogfood graph carried it), so this rename is a vocabulary change, not a data migration." }
|
|
10266
|
+
],
|
|
10103
10267
|
// (target version TBD at release ceremony — pencilled at 0.22.0, the next
|
|
10104
10268
|
// breaking slot after 0.21.0 per the docket; adjust the key if Wave 4
|
|
10105
10269
|
// claims it first.) WS3 commit-provenance collapse (2026-07-05, ratified):
|
|
@@ -11653,9 +11817,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11653
11817
|
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Urgency relative to other work. Independent of `bug_severity` (a critical bug can have low priority if rare)." },
|
|
11654
11818
|
assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
|
|
11655
11819
|
due_date: { type: "string", description: "ISO date due. Often tied to a release gate or SLA." },
|
|
11656
|
-
labels: { type: "string[]", description: "Free-form classification tags.
|
|
11820
|
+
labels: { type: "string[]", description: "Free-form classification tags. @deprecated since 0.32.0. Use base-node `tags` for ungrouped labels, and `classification_axis` + `classification_value` + `node_classified_as_classification_value` when the labels belong to a named group. This field duplicated `tags` and had no consumers; three parallel label surfaces (base `tags`, per-type `tags`, per-type `labels`) were two too many. `UPG_PROPERTY_MIGRATIONS['0.32.0']` drops it." },
|
|
11657
11821
|
workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
|
|
11658
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
11822
|
+
workflow_state_category: { type: "string", enum: ["triage", "backlog", "unstarted", "started", "completed", "cancelled"], description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning: a source "In Review" and a source "QA" may both map to a verification phase. Optional companion to `workflow_state`; canonical `status` remains the sole reasoning axis.', notes: "It exists so a graph can reason over an imported custom workflow WITHOUT promoting the source's raw label to `status`. The raw label keeps its own field and stays verbatim; this one says what that label means in the six-bucket vocabulary every major tracker converges on. NARROWED FROM `string` AT 0.32.0. The field exists to carry exactly the vocabulary, and typing it as an open string meant nothing enforced the one thing it was for; an importer could write any word here and no consumer would know it had. A graph carrying a free string now fails to type-check rather than failing to be understood." }
|
|
11659
11823
|
},
|
|
11660
11824
|
// BuildArtifactProperties: Build artifact.
|
|
11661
11825
|
build_artifact: {
|
|
@@ -11686,6 +11850,16 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11686
11850
|
allocated: { type: "number", description: "Capacity already allocated to work" },
|
|
11687
11851
|
available: { type: "number", description: "Remaining unallocated capacity" }
|
|
11688
11852
|
},
|
|
11853
|
+
// CaptureProperties: Capture: a dated, hashed rendition of something already in the graph.
|
|
11854
|
+
capture: {
|
|
11855
|
+
capture_uri: { type: "string", description: "Where the bytes are. `https://` for hosted, `file://` or a relative path for local. Required: a capture with no location renders nothing." },
|
|
11856
|
+
content_hash: { type: "string", description: "Content hash of the bytes. The regeneration signal; see the type's `@remarks` for why mtime and size are not." },
|
|
11857
|
+
hash_algorithm: { type: "string", enum: ["sha256", "sha1", "md5"], description: "Hash algorithm. Absent means `sha256`." },
|
|
11858
|
+
captured_at: { type: "string", description: "ISO timestamp the capture was taken." },
|
|
11859
|
+
media_type: { type: "string", description: 'IANA media type of the bytes. @example "image/png"' },
|
|
11860
|
+
fidelity: { type: "string", enum: ["exact", "approximate", "not_applicable"], description: "How faithfully this rendition represents its subject. `approximate` covers a capture taken in a stand-in state or at the wrong viewport." },
|
|
11861
|
+
capture_status: { type: "string", enum: ["captured", "blocked", "skipped"], description: "Whether the capture succeeded.", notes: "A `blocked` capture is a real record rather than a missing one: it says the subject exists and could not be rendered, which is what stops the next run rediscovering the same obstacle. `skipped` is the deliberate exclusion." }
|
|
11862
|
+
},
|
|
11689
11863
|
// CeremonyProperties: Ceremony entity.
|
|
11690
11864
|
ceremony: {
|
|
11691
11865
|
ceremony_type: { type: "string", enum: ["standup", "planning", "review", "retro", "sync", "demo", "other"], description: "Kind of recurring meeting" },
|
|
@@ -11889,7 +12063,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11889
12063
|
members: { type: "object[]", description: "The frozen member arrangement, captured at publish. Layout and pointers only, never resolved content." },
|
|
11890
12064
|
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." },
|
|
11891
12065
|
published_at: { type: "string", description: "ISO timestamp of the most recent publish or republish." },
|
|
11892
|
-
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
12066
|
+
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." },
|
|
12067
|
+
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 composition shows. `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 composition with no `member_query` is authored, which is what every composition written before 0.32.0 is." },
|
|
12068
|
+
presentation: { type: "object", description: "Advisory rendering intent for the composition as a whole. A consumer may ignore it entirely and still be conformant." }
|
|
11893
12069
|
},
|
|
11894
12070
|
// ConfigurationAxisProperties: A named dimension along which the product's composition differs.
|
|
11895
12071
|
configuration_axis: {
|
|
@@ -12346,7 +12522,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12346
12522
|
start_date: { type: "string", description: "ISO date work begins" },
|
|
12347
12523
|
target_date: { type: "string", description: "ISO date work completes" },
|
|
12348
12524
|
workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
|
|
12349
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
12525
|
+
workflow_state_category: { type: "string", enum: ["triage", "backlog", "unstarted", "started", "completed", "cancelled"], description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning: a source "In Review" and a source "QA" may both map to a verification phase. Optional companion to `workflow_state`; canonical `status` remains the sole reasoning axis.', notes: "It exists so a graph can reason over an imported custom workflow WITHOUT promoting the source's raw label to `status`. The raw label keeps its own field and stays verbatim; this one says what that label means in the six-bucket vocabulary every major tracker converges on. NARROWED FROM `string` AT 0.32.0. The field exists to carry exactly the vocabulary, and typing it as an open string meant nothing enforced the one thing it was for; an importer could write any word here and no consumer would know it had. A graph carrying a free string now fails to type-check rather than failing to be understood." }
|
|
12350
12526
|
},
|
|
12351
12527
|
// ErrorBudgetProperties: Error budget.
|
|
12352
12528
|
error_budget: {
|
|
@@ -12530,7 +12706,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12530
12706
|
target_date: { type: "string", description: "ISO date work completes" },
|
|
12531
12707
|
health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" },
|
|
12532
12708
|
workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
|
|
12533
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
12709
|
+
workflow_state_category: { type: "string", enum: ["triage", "backlog", "unstarted", "started", "completed", "cancelled"], description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning: a source "In Review" and a source "QA" may both map to a verification phase. Optional companion to `workflow_state`; canonical `status` remains the sole reasoning axis.', notes: "It exists so a graph can reason over an imported custom workflow WITHOUT promoting the source's raw label to `status`. The raw label keeps its own field and stays verbatim; this one says what that label means in the six-bucket vocabulary every major tracker converges on. NARROWED FROM `string` AT 0.32.0. The field exists to carry exactly the vocabulary, and typing it as an open string meant nothing enforced the one thing it was for; an importer could write any word here and no consumer would know it had. A graph carrying a free string now fails to type-check rather than failing to be understood." }
|
|
12534
12710
|
},
|
|
12535
12711
|
// FeatureAreaProperties: A structural grouping of related features within a product.
|
|
12536
12712
|
feature_area: {
|
|
@@ -13526,7 +13702,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13526
13702
|
url: { type: "string", description: "Where the product lives. Marketing site, app store URL, etc.", modifier: "volatile" },
|
|
13527
13703
|
logo_url: { type: "string", description: "Logo or icon URL. Used to render product cards and lists.", modifier: "volatile" },
|
|
13528
13704
|
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" },
|
|
13529
|
-
described_configuration: { type: "string", description: 'The product configuration this graph describes, named in plain language. A label for readers: nothing reads it to gate, filter, or alter how a tool interprets the graph. @example "Enterprise plan, split-navigation flag on"', notes: "A product's composition often differs by feature flag, plan tier, permission level or beta programme: surfaces appear, disappear, or are replaced by different surfaces with different occupants. A graph that models one of those without saying which is qualified by a condition nobody wrote down, and every fact in it inherits that silence. It makes no claim about which configuration most customers are on. Where a product genuinely ships several and the differences matter, declare a `configuration_axis` instead and let each fact say which values it holds under." }
|
|
13705
|
+
described_configuration: { type: "string", description: 'The product configuration this graph describes, named in plain language. A label for readers: nothing reads it to gate, filter, or alter how a tool interprets the graph. @example "Enterprise plan, split-navigation flag on"', notes: "A product's composition often differs by feature flag, plan tier, permission level or beta programme: surfaces appear, disappear, or are replaced by different surfaces with different occupants. A graph that models one of those without saying which is qualified by a condition nobody wrote down, and every fact in it inherits that silence. It makes no claim about which configuration most customers are on. Where a product genuinely ships several and the differences matter, declare a `configuration_axis` instead and let each fact say which values it holds under." },
|
|
13706
|
+
key_prefix: { type: "string", description: 'Prefix for keys minted onto this product\'s nodes (e.g. `"LTN"`, giving `LTN-1`, `LTN-2`, \u2026). A product with no prefix mints no keys. @example "LTN"', notes: 'Pairs with `UPGBaseNode.key`, which holds the minted value. The prefix sits on the product because the key\'s uniqueness scope is the product, across entity types; the namespace a tracker calls a "team". Only the prefix is serialised: the next number is `max(existing) + 1`, derived from the graph, because a counter is a fact about a store rather than about the product.' }
|
|
13530
13707
|
},
|
|
13531
13708
|
// ProductAreaProperties: ProductArea entity.
|
|
13532
13709
|
product_area: {
|
|
@@ -14126,9 +14303,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14126
14303
|
effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
|
|
14127
14304
|
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other tasks" },
|
|
14128
14305
|
due_date: { type: "string", description: "ISO date due. Typically bounded by the containing story's due date." },
|
|
14129
|
-
labels: { type: "string[]", description: "Free-form classification tags.
|
|
14306
|
+
labels: { type: "string[]", description: "Free-form classification tags. @deprecated since 0.32.0. Use base-node `tags` for ungrouped labels, and `classification_axis` + `classification_value` + `node_classified_as_classification_value` when the labels belong to a named group. This field duplicated `tags` and had no consumers; three parallel label surfaces (base `tags`, per-type `tags`, per-type `labels`) were two too many. `UPG_PROPERTY_MIGRATIONS['0.32.0']` drops it." },
|
|
14130
14307
|
workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
|
|
14131
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
14308
|
+
workflow_state_category: { type: "string", enum: ["triage", "backlog", "unstarted", "started", "completed", "cancelled"], description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning: a source "In Review" and a source "QA" may both map to a verification phase. Optional companion to `workflow_state`; canonical `status` remains the sole reasoning axis.', notes: "It exists so a graph can reason over an imported custom workflow WITHOUT promoting the source's raw label to `status`. The raw label keeps its own field and stays verbatim; this one says what that label means in the six-bucket vocabulary every major tracker converges on. NARROWED FROM `string` AT 0.32.0. The field exists to carry exactly the vocabulary, and typing it as an open string meant nothing enforced the one thing it was for; an importer could write any word here and no consumer would know it had. A graph carrying a free string now fails to type-check rather than failing to be understood." }
|
|
14132
14309
|
},
|
|
14133
14310
|
// TeamProperties: Team entity.
|
|
14134
14311
|
team: {
|
|
@@ -14350,7 +14527,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14350
14527
|
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other stories. Lifted onto user_story (0.20.0) so the story is a first-class plannable unit alongside task, matching Jira/Linear where the story/issue is the estimated-and-assigned atom." },
|
|
14351
14528
|
due_date: { type: "string", description: "ISO date due. Typically bounded by the release or planning_cycle the story is scheduled into." },
|
|
14352
14529
|
workflow_state: { type: "string", description: 'The source tool\'s raw custom workflow state, verbatim and opaque (e.g. "In Review", "QA", "Needs Triage"). Non-canonical and never reasoned over: it exists to round-trip an import losslessly. Map it onto a canonical bucket with `workflow_state_category`; canonical `status` stays the sole reasoning axis.' },
|
|
14353
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
14530
|
+
workflow_state_category: { type: "string", enum: ["triage", "backlog", "unstarted", "started", "completed", "cancelled"], description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning: a source "In Review" and a source "QA" may both map to a verification phase. Optional companion to `workflow_state`; canonical `status` remains the sole reasoning axis.', notes: "It exists so a graph can reason over an imported custom workflow WITHOUT promoting the source's raw label to `status`. The raw label keeps its own field and stays verbatim; this one says what that label means in the six-bucket vocabulary every major tracker converges on. NARROWED FROM `string` AT 0.32.0. The field exists to carry exactly the vocabulary, and typing it as an open string meant nothing enforced the one thing it was for; an importer could write any word here and no consumer would know it had. A graph carrying a free string now fails to type-check rather than failing to be understood." }
|
|
14354
14531
|
},
|
|
14355
14532
|
// ValueObjectProperties: DDD value object.
|
|
14356
14533
|
value_object: {
|
|
@@ -14465,8 +14642,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14465
14642
|
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." },
|
|
14466
14643
|
owner: { type: "string", description: "Workspace owner (handle or email). Display label; canonical owner is `team_owns_workspace` or `persona_owns_workspace`." },
|
|
14467
14644
|
member_count: { type: "number", description: "Snapshot count. `team_works_in_workspace` edges are the source of truth.", modifier: "derived" },
|
|
14468
|
-
archived: { type: "boolean", description: "Archived. Archived workspaces remain queryable but hidden from default views." },
|
|
14469
|
-
archived_at: { type: "string", description: "ISO timestamp archived. Pairs with `archived === true`." },
|
|
14645
|
+
archived: { type: "boolean", description: "Archived. Archived workspaces remain queryable but hidden from default views. @deprecated since 0.32.0. Use `UPGBaseNode.archived`, which generalises this pair to every entity type. Field data showed the archived/status split is not a workspace peculiarity: a tracker held 559 archived-completed items beside 18 live-completed ones, and one status field has nowhere to put the difference. `UPG_PROPERTY_MIGRATIONS['0.32.0']` lifts this value to the top level." },
|
|
14646
|
+
archived_at: { type: "string", description: "ISO timestamp archived. Pairs with `archived === true`. @deprecated since 0.32.0. Use `UPGBaseNode.archived_at`. Lifted by `UPG_PROPERTY_MIGRATIONS['0.32.0']`." },
|
|
14470
14647
|
icon: { type: "string", description: "Display icon (emoji or icon name)" },
|
|
14471
14648
|
retention: { type: "string", enum: ["transient", "durable"], description: "Retention intent. Absent means `transient`.", notes: "A workspace is a free-form thinking space and is transient by default, so the spec makes both intents EXPRESSIBLE and takes no position on which reaches the file. Whether to write a transient canvas at all is a tool decision, and the recommended posture is not to: a scratch canvas nobody named has no business in a shared, git-tracked graph, where it lands in everyone's diff. Writing only `durable` workspaces is what keeps a canonical graph entity comfortable with a transient-by-default object." },
|
|
14472
14649
|
canvas: { type: "object", description: "Opaque canvas furniture: the parts of a canvas with no graph referent. Preserved verbatim on round-trip and never interpreted. Tool extension keys are namespaced `<tool>:<key>` with a colon, and no consumer interprets a key it does not own.", notes: "The cut here is UPG principle P14 applied literally. Anything that is a REFERENCE TO A GRAPH NODE is an edge, which is why placed entities ride `workspace_arranges_node` and not this bag: a node id held as a scalar inside a blob is a foreign key in disguise, and a deleted node would leave a stale reference nothing can detect. Anything that is pure UI chrome with no graph referent stays here, because minting `annotation` and `frame` entity types would add catalog surface that is meaningless to every consumer outside the tool that drew it. WHY THE COLON, and why the rule is in the summary rather than buried here. An underscore key is indistinguishable from an ordinary property name, so a migration that targets namespaced keys cannot match it and no validator can detect one that should have been namespaced. That undetectability is why enforcement lives in the type and the documentation instead of in a check: a check would report clean on a bag full of underscore keys, which is worse than no check. Two conventions were already coexisting when this was ruled (`entopo_views` in a live writer, `entopo:view_blocks` in the contract test certifying preservation), which is how a cheap rule becomes a migration. PRESERVATION IS NOT PERMISSION TO RENDER. Preserving every byte says nothing about meaning: a field-measured canvas carried `excluded: true` tombstones, and a consumer that preserved them faithfully while rendering every entry showed the user images they had deleted." }
|
|
@@ -21870,6 +22047,7 @@ var ENTITY_EMOJI = {
|
|
|
21870
22047
|
competitor_signal: "\u{1F4E1}",
|
|
21871
22048
|
compliance_framework: "\u{1F3DF}\uFE0F",
|
|
21872
22049
|
compliance_requirement: "\u26F2",
|
|
22050
|
+
capture: "\u{1F4F8}",
|
|
21873
22051
|
composition: "\u{1F5BC}\uFE0F",
|
|
21874
22052
|
constraint: "\u{1F512}",
|
|
21875
22053
|
contact: "\u{1F4C7}",
|
|
@@ -25249,9 +25427,9 @@ var PRODUCT_SPEC_GUIDE = {
|
|
|
25249
25427
|
},
|
|
25250
25428
|
{
|
|
25251
25429
|
name: "Planning Cadence",
|
|
25252
|
-
description: "The cadence axis. Create a planning_cycle (a sprint, iteration, quarter, or PI), nest finer cycles inside a coarse one, schedule the
|
|
25253
|
-
entity_types: ["planning_cycle", "user_story", "objective"],
|
|
25254
|
-
edge_chain: ["planning_cycle_contains_planning_cycle", "
|
|
25430
|
+
description: "The cadence axis. Create a planning_cycle (a sprint, iteration, quarter, or PI), nest finer cycles inside a coarse one, schedule the work that flows through it (a task, story, epic, feature or bug), and scope the objectives the cycle serves. Scheduling and scoping are deliberate links, not containment: the scheduled item keeps its feature/epic parent.",
|
|
25431
|
+
entity_types: ["planning_cycle", "task", "user_story", "objective"],
|
|
25432
|
+
edge_chain: ["planning_cycle_contains_planning_cycle", "planning_cycle_schedules_work_item", "objective_scoped_to_planning_cycle"]
|
|
25255
25433
|
}
|
|
25256
25434
|
],
|
|
25257
25435
|
required_bridges: [
|
|
@@ -25948,7 +26126,7 @@ var PORTFOLIO_GUIDE = {
|
|
|
25948
26126
|
var WORKSPACE_GUIDE = {
|
|
25949
26127
|
domain_id: "workspace",
|
|
25950
26128
|
anchor_entity: "workspace",
|
|
25951
|
-
creation_sequence: ["workspace", "framework_exercise", "composition"],
|
|
26129
|
+
creation_sequence: ["workspace", "framework_exercise", "composition", "capture"],
|
|
25952
26130
|
patterns: [
|
|
25953
26131
|
{
|
|
25954
26132
|
name: "Canvas to published view",
|
|
@@ -28562,7 +28740,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28562
28740
|
{
|
|
28563
28741
|
id: "planning-cycle-without-scheduled-work",
|
|
28564
28742
|
name: "Planning cycle with no scheduled work",
|
|
28565
|
-
description: "A planning_cycle exists but neither schedules any
|
|
28743
|
+
description: "A planning_cycle exists but neither schedules any work item nor contains a finer sub-cycle. An empty cadence box is a date range with nothing flowing through it: a sprint or iteration nobody planned work into, or a coarse period that was never broken down.",
|
|
28566
28744
|
structured_condition: {
|
|
28567
28745
|
operator: "and",
|
|
28568
28746
|
checks: [
|
|
@@ -28571,8 +28749,8 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28571
28749
|
check: {
|
|
28572
28750
|
type: "relationship",
|
|
28573
28751
|
source_type: "planning_cycle",
|
|
28574
|
-
edge_type: "
|
|
28575
|
-
target_type: "
|
|
28752
|
+
edge_type: "planning_cycle_schedules_work_item",
|
|
28753
|
+
target_type: "node",
|
|
28576
28754
|
comparison: "not_exists"
|
|
28577
28755
|
}
|
|
28578
28756
|
},
|
|
@@ -28588,10 +28766,16 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28588
28766
|
]
|
|
28589
28767
|
},
|
|
28590
28768
|
why_it_matters: "A cadence layer only earns its keep when work is planned through it. An interval with no scheduled stories and no nested cycles adds ceremony without telling anyone what the period is for.",
|
|
28591
|
-
remediation: "Schedule the
|
|
28769
|
+
remediation: "Schedule the work the cycle will carry via `planning_cycle_schedules_work_item`, or break a coarse period into finer cycles via `planning_cycle_contains_planning_cycle`. If neither applies, the interval is not yet a real cadence box.",
|
|
28592
28770
|
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
28593
28771
|
severity: "low",
|
|
28594
28772
|
since: "0.20.0"
|
|
28773
|
+
// RETARGETED at 0.32.0 from planning_cycle_schedules_user_story. Left
|
|
28774
|
+
// unchanged it would have become a false-positive generator the moment
|
|
28775
|
+
// cycles could hold tasks: a cycle full of scheduled tasks and no stories
|
|
28776
|
+
// would have reported as empty. Labeled fixtures cover the near-miss this
|
|
28777
|
+
// is most likely to get wrong — a coarse `period` cycle that legitimately
|
|
28778
|
+
// holds only sub-cycles and schedules nothing directly.
|
|
28595
28779
|
},
|
|
28596
28780
|
// ── Market intelligence layer ───────────────────────────────────────────
|
|
28597
28781
|
{
|
|
@@ -30717,7 +30901,7 @@ var UPG_REGIONS = [
|
|
|
30717
30901
|
"roadmap_item_references_feature",
|
|
30718
30902
|
"feature_request_voted_on_by_feedback_vote",
|
|
30719
30903
|
"planning_cycle_contains_planning_cycle",
|
|
30720
|
-
"
|
|
30904
|
+
"planning_cycle_schedules_work_item"
|
|
30721
30905
|
],
|
|
30722
30906
|
boundary_edges: [
|
|
30723
30907
|
{
|
|
@@ -32645,9 +32829,12 @@ var NODE_KEY_ORDER = [
|
|
|
32645
32829
|
"title",
|
|
32646
32830
|
"slug",
|
|
32647
32831
|
"aliases",
|
|
32832
|
+
"key",
|
|
32648
32833
|
"description",
|
|
32649
32834
|
"tags",
|
|
32650
32835
|
"status",
|
|
32836
|
+
"archived",
|
|
32837
|
+
"archived_at",
|
|
32651
32838
|
"lifecycle_status",
|
|
32652
32839
|
"source_id",
|
|
32653
32840
|
"source_type",
|
|
@@ -32937,7 +33124,7 @@ function checkHeaderSealText(text) {
|
|
|
32937
33124
|
}
|
|
32938
33125
|
|
|
32939
33126
|
// src/index.ts
|
|
32940
|
-
var UPG_VERSION = "0.
|
|
33127
|
+
var UPG_VERSION = "0.32.0";
|
|
32941
33128
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
32942
33129
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
32943
33130
|
var UPG_TYPES = getTypes();
|