@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.js
CHANGED
|
@@ -491,7 +491,7 @@ var UPG_DOMAINS = [
|
|
|
491
491
|
id: "workspace",
|
|
492
492
|
label: "Workspace",
|
|
493
493
|
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.",
|
|
494
|
-
types: ["workspace", "framework_exercise", "composition"]
|
|
494
|
+
types: ["workspace", "framework_exercise", "composition", "capture"]
|
|
495
495
|
},
|
|
496
496
|
{
|
|
497
497
|
id: "foundations",
|
|
@@ -979,7 +979,10 @@ var UPG_ENTITY_META = [
|
|
|
979
979
|
// ── Workspace ──
|
|
980
980
|
{ name: "workspace", type_id: "ent_328", maturity: "proposed", since: "0.2.0" },
|
|
981
981
|
{ name: "framework_exercise", type_id: "ent_350", maturity: "proposed", since: "0.8.4" },
|
|
982
|
-
{ name: "composition", type_id: "ent_361", maturity: "proposed", since: "0.31.0" }
|
|
982
|
+
{ name: "composition", type_id: "ent_361", maturity: "proposed", since: "0.31.0" },
|
|
983
|
+
// A dated, hashed rendition of a node that is already in the graph. Enters
|
|
984
|
+
// `proposed` per the maturity rubric (precedent: composition 0.31.0).
|
|
985
|
+
{ name: "capture", type_id: "ent_362", maturity: "proposed", since: "0.32.0" }
|
|
983
986
|
];
|
|
984
987
|
var DEFERRED_PROPOSED_BY_CONTRACT = {
|
|
985
988
|
// user_story (UPG-571) and experiment_run (UPG-664) graduated to stable in
|
|
@@ -1267,6 +1270,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
1267
1270
|
// the catalog's singular-source/target grammar (polymorphic targets
|
|
1268
1271
|
// would be a separate catalog-mechanic change).
|
|
1269
1272
|
competitive_analysis_dimensioned_by_classification_axis: { forward_verb: "dimensioned_by", reverse_verb: "dimensions", classification: "hierarchy", source_type: "competitive_analysis", target_type: "classification_axis" },
|
|
1273
|
+
// A product's own classification axes (0.32.0). Before this the only parent a
|
|
1274
|
+
// classification_axis could have was a competitive_analysis, so a taxonomy the
|
|
1275
|
+
// product uses on its OWN entities — the grouped-label case, one named group
|
|
1276
|
+
// over a set of values — had to hang off a competitive analysis it has nothing
|
|
1277
|
+
// to do with, or float parentless.
|
|
1278
|
+
//
|
|
1279
|
+
// Hierarchy, so the axis has a home in get_tree, on the same honest verb the
|
|
1280
|
+
// competitive_analysis edge already uses; and it earns its
|
|
1281
|
+
// UPG_VALID_CHILDREN.product pair the same way product_defines_configuration_axis
|
|
1282
|
+
// did at 0.30.0 — axes are few per product, and visibility of a mechanism
|
|
1283
|
+
// matters more than the tree cost.
|
|
1284
|
+
//
|
|
1285
|
+
// This is what makes one level of label grouping expressible with NO new label
|
|
1286
|
+
// surface: the group is the axis, the labels are its values, and carrying one
|
|
1287
|
+
// is node_classified_as_classification_value, all of it stable since 0.4.0.
|
|
1288
|
+
// Ungrouped freeform labels stay in `tags`. The line between them: if the
|
|
1289
|
+
// group has a name a person would filter by, it is an axis; otherwise a tag.
|
|
1290
|
+
product_dimensioned_by_classification_axis: { forward_verb: "dimensioned_by", reverse_verb: "dimensions", classification: "hierarchy", source_type: "product", target_type: "classification_axis" },
|
|
1270
1291
|
classification_axis_includes_classification_value: { forward_verb: "includes", reverse_verb: "value_of", classification: "hierarchy", source_type: "classification_axis", target_type: "classification_value" },
|
|
1271
1292
|
// 0.10.2: also dual-registered as a cross-edge (UPG_CROSS_EDGE_TYPES) so a
|
|
1272
1293
|
// competitor can be classified directly against a `registry/{classification_value}`
|
|
@@ -1506,10 +1527,20 @@ var UPG_EDGE_CATALOG = {
|
|
|
1506
1527
|
// iterations; a cycle contains its cooldown), mirroring team_contains_team.
|
|
1507
1528
|
planning_cycle_contains_planning_cycle: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "planning_cycle", target_type: "planning_cycle" },
|
|
1508
1529
|
// E3: scheduling work into a cycle is a deliberate authoring act, not a
|
|
1509
|
-
// containment nesting —
|
|
1510
|
-
// referenced by the cycle. deliberate_only so the generic-inference
|
|
1511
|
-
// never auto-materialise it.
|
|
1512
|
-
|
|
1530
|
+
// containment nesting — the work item keeps its feature/epic parent and is
|
|
1531
|
+
// merely referenced by the cycle. deliberate_only so the generic-inference
|
|
1532
|
+
// chokepoints never auto-materialise it.
|
|
1533
|
+
//
|
|
1534
|
+
// WIDENED AT 0.32.0, renamed from planning_cycle_schedules_user_story. The
|
|
1535
|
+
// story-only endpoint could not hold a real tracker import: the adapters
|
|
1536
|
+
// default an unrecognised issue to `task`, so the type a cycle most needed to
|
|
1537
|
+
// schedule was the one type it could not reach. Endpoint-polymorphic over the
|
|
1538
|
+
// work-item set {feature, epic, user_story, task, bug} by the same
|
|
1539
|
+
// construction as work_item_blocks_work_item in the same 0.20.0 batch — the
|
|
1540
|
+
// `work_item` token names the intended semantic domain, the endpoint is the
|
|
1541
|
+
// `node` wildcard. Three typed edges were the alternative and would have
|
|
1542
|
+
// re-opened a decision this vocabulary already took for the same entity set.
|
|
1543
|
+
planning_cycle_schedules_work_item: { forward_verb: "schedules", reverse_verb: "scheduled_in", classification: "semantic", source_type: "planning_cycle", target_type: "node", deliberate_only: true },
|
|
1513
1544
|
// E2: the OKR cycle-scoping anchor. objective -> planning_cycle. planning_cycle
|
|
1514
1545
|
// is portfolio_shared, so this passes the cross-scope gate and ships as
|
|
1515
1546
|
// PROVISIONAL (allowed cross-product with a write-time warning, self-maintaining,
|
|
@@ -2634,6 +2665,16 @@ var UPG_EDGE_CATALOG = {
|
|
|
2634
2665
|
// NOT cross_product_eligible: a portfolio-level composition spanning several
|
|
2635
2666
|
// product graphs is conceivable but unevidenced. Same YAGNI call as above.
|
|
2636
2667
|
composition_focuses_node: { forward_verb: "focuses", reverse_verb: "focused_in", classification: "cross-domain", source_type: "composition", target_type: "node" },
|
|
2668
|
+
// What a capture is a picture OF (0.32.0). Polymorphic because anything in the
|
|
2669
|
+
// graph can be rendered — a surface, a screen, a report, a canvas — and being
|
|
2670
|
+
// captured carries no structural role, so the endpoint collapses to the
|
|
2671
|
+
// wildcard rather than enumerating a list that would never finish.
|
|
2672
|
+
//
|
|
2673
|
+
// This edge is also where the preservation-is-not-permission rule lands for
|
|
2674
|
+
// renditions: a consumer renders the captures the GRAPH says exist, never the
|
|
2675
|
+
// ones some tool's opaque bag still remembers. A capture that was deleted is a
|
|
2676
|
+
// node that was deleted, and the spec needs no second deletion vocabulary.
|
|
2677
|
+
capture_renders_node: { forward_verb: "renders", reverse_verb: "rendered_by", classification: "cross-domain", source_type: "capture", target_type: "node" },
|
|
2637
2678
|
// ── P14 edges (replacing foreign-key properties) ─────────────────────────
|
|
2638
2679
|
feature_addresses_job: { forward_verb: "addresses", reverse_verb: "addressed_by", classification: "cross-domain", source_type: "feature", target_type: "job" },
|
|
2639
2680
|
feature_drives_key_result: { forward_verb: "drives", reverse_verb: "driven_by", classification: "cross-domain", source_type: "feature", target_type: "key_result" },
|
|
@@ -2750,6 +2791,23 @@ var UPG_EDGE_CATALOG = {
|
|
|
2750
2791
|
node_owned_by_stakeholder: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "stakeholder" },
|
|
2751
2792
|
node_owned_by_department: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "department", cross_product_eligible: true },
|
|
2752
2793
|
node_owned_by_person: { forward_verb: "owned_by", reverse_verb: "owns", classification: "cross-domain", source_type: "node", target_type: "person" },
|
|
2794
|
+
// ── Cross-domain: Assignment (0.32.0) ───────────────────────────────────────
|
|
2795
|
+
// Assignment is NOT ownership, and 0.12.0 ruled otherwise. That ruling routed
|
|
2796
|
+
// task.assignee and bug.assignee into node_owned_by_person, and it is
|
|
2797
|
+
// superseded here rather than quietly widened, because the two differ in a way
|
|
2798
|
+
// the field measures.
|
|
2799
|
+
//
|
|
2800
|
+
// ASSIGNMENT HAS A TIME INTERVAL AND OWNERSHIP DOES NOT. A shipped ops plane
|
|
2801
|
+
// models assignment as a claim carrying claimed-at, released-at and a partial
|
|
2802
|
+
// unique index enforcing one live claim; nothing in the spec gives ownership
|
|
2803
|
+
// that shape. And a real 184-item board ran with 157 items (85%) unassigned
|
|
2804
|
+
// while every one of them was owned in the ordinary sense. One edge meaning
|
|
2805
|
+
// both cannot express "owned by the team, assigned to nobody", which is the
|
|
2806
|
+
// modal state of a working board.
|
|
2807
|
+
//
|
|
2808
|
+
// Not cross_product_eligible, matching node_owned_by_person: an assignee is a
|
|
2809
|
+
// person in this graph's own roster.
|
|
2810
|
+
node_assigned_to_person: { forward_verb: "assigned_to", reverse_verb: "assigned", classification: "cross-domain", source_type: "node", target_type: "person" },
|
|
2753
2811
|
// ── Cross-domain: Architecture (DDD) ─────────────────────────────────────────
|
|
2754
2812
|
// Service, domain_event, domain_entity, aggregate, read_model, api_contract,
|
|
2755
2813
|
// value_object, command, and data_model are DDD building blocks that all
|
|
@@ -3694,7 +3752,18 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
|
3694
3752
|
// onto a canvas or shown in a published view, and neither carries a
|
|
3695
3753
|
// structural role, so both collapse polymorphic rather than enumerating.
|
|
3696
3754
|
"workspace_arranges_node",
|
|
3697
|
-
"composition_focuses_node"
|
|
3755
|
+
"composition_focuses_node",
|
|
3756
|
+
// Universal assignment (0.32.0): who is working on this, as distinct from who
|
|
3757
|
+
// owns it. Wildcard source for the same reason node_owned_by_person has one.
|
|
3758
|
+
"node_assigned_to_person",
|
|
3759
|
+
// Capture subject (0.32.0): anything in the graph can be rendered, and being
|
|
3760
|
+
// rendered carries no structural role.
|
|
3761
|
+
"capture_renders_node",
|
|
3762
|
+
// Cadence scheduling (0.32.0, widened from planning_cycle_schedules_user_story):
|
|
3763
|
+
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
3764
|
+
// bug}. The `work_item` key token names the intended semantic domain; the
|
|
3765
|
+
// endpoint is the `node` wildcard.
|
|
3766
|
+
"planning_cycle_schedules_work_item"
|
|
3698
3767
|
];
|
|
3699
3768
|
var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
|
|
3700
3769
|
function isPolymorphicEdge(key) {
|
|
@@ -3889,6 +3958,11 @@ var UPG_VALID_CHILDREN = {
|
|
|
3889
3958
|
// the product renders. Few per product, and top-level because an axis is a
|
|
3890
3959
|
// product-wide fact rather than a child of any one feature or surface.
|
|
3891
3960
|
"configuration_axis",
|
|
3961
|
+
// Classification (0.32.0): the product's own taxonomies — the grouped-label
|
|
3962
|
+
// case, one named group over a set of values. Same reasoning as the
|
|
3963
|
+
// configuration axis above, and until now the only parent a
|
|
3964
|
+
// classification_axis could have was a competitive_analysis.
|
|
3965
|
+
"classification_axis",
|
|
3892
3966
|
// Engineering
|
|
3893
3967
|
"bounded_context",
|
|
3894
3968
|
"code_repository",
|
|
@@ -4173,7 +4247,7 @@ var UPG_VALID_CHILDREN = {
|
|
|
4173
4247
|
// contains iterations; a cycle contains its cooldown) via
|
|
4174
4248
|
// planning_cycle_contains_planning_cycle. It does NOT contain user_story as a
|
|
4175
4249
|
// child: scheduling work into a cycle is the semantic, deliberate-only
|
|
4176
|
-
//
|
|
4250
|
+
// planning_cycle_schedules_work_item edge, so the item keeps its feature/epic
|
|
4177
4251
|
// containment parent and is merely referenced by the cycle.
|
|
4178
4252
|
planning_cycle: ["planning_cycle"],
|
|
4179
4253
|
// ── Engineering hierarchy ───────────────────────────────────────────────────
|
|
@@ -4519,7 +4593,12 @@ var UPG_CONTAINMENT_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
4519
4593
|
// part of the product, it is a lens onto one. This is also what keeps the
|
|
4520
4594
|
// type to two edges rather than three: no provenance edge is minted (that is
|
|
4521
4595
|
// `workspace_produced_node`, reused) and no parent edge is needed.
|
|
4522
|
-
"composition"
|
|
4596
|
+
"composition",
|
|
4597
|
+
// A capture has no structural parent for the same reason: it anchors to what
|
|
4598
|
+
// it RENDERS, via `capture_renders_node`. Giving it a product parent would
|
|
4599
|
+
// claim a capture is part of the product rather than a picture of one, and
|
|
4600
|
+
// would mint a containment edge whose only job is to satisfy the hierarchy.
|
|
4601
|
+
"capture"
|
|
4523
4602
|
]);
|
|
4524
4603
|
function isContainmentFreeType(entityType) {
|
|
4525
4604
|
return UPG_CONTAINMENT_FREE_TYPES.has(entityType);
|
|
@@ -6625,7 +6704,8 @@ var WORK_ITEM_TEMPLATE = {
|
|
|
6625
6704
|
initial_phase: "todo",
|
|
6626
6705
|
terminal_phases: ["done", "cancelled"],
|
|
6627
6706
|
phases: [
|
|
6628
|
-
{ id: "
|
|
6707
|
+
{ 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"] },
|
|
6708
|
+
{ id: "todo", status_category: "unstarted", label: "To Do", description: "Identified but not yet started.", transitions_to: ["in_progress", "backlog", "cancelled"] },
|
|
6629
6709
|
{ id: "in_progress", status_category: "started", label: "In Progress", description: "Actively being worked on.", transitions_to: ["in_review", "todo", "cancelled"] },
|
|
6630
6710
|
{ id: "in_review", status_category: "started", label: "In Review", description: "Work completed, awaiting review or acceptance.", transitions_to: ["done", "in_progress", "cancelled"] },
|
|
6631
6711
|
{ id: "done", status_category: "completed", label: "Done", description: "Completed and accepted.", transitions_to: [] },
|
|
@@ -7256,7 +7336,11 @@ var UPG_LIFECYCLE_FREE_TYPES = /* @__PURE__ */ new Set([
|
|
|
7256
7336
|
// from experiment_runs (which have lifecycles); hypothesis_evidence was
|
|
7257
7337
|
// lifecycle-free here but is deprecated at v0.4.0 ──────────────────────────────────────
|
|
7258
7338
|
"evidence",
|
|
7259
|
-
"learning"
|
|
7339
|
+
"learning",
|
|
7340
|
+
// ── Workspace (1 of 4): a capture is a fact about a moment, not a workflow.
|
|
7341
|
+
// It is taken, and from then on it either still matches its subject or it
|
|
7342
|
+
// does not — which is what `content_hash` answers, not a phase. ─────────
|
|
7343
|
+
"capture"
|
|
7260
7344
|
]);
|
|
7261
7345
|
function isLifecycleFreeType(entityType) {
|
|
7262
7346
|
return UPG_LIFECYCLE_FREE_TYPES.has(entityType);
|
|
@@ -8160,6 +8244,46 @@ function getDeprecatedTypes() {
|
|
|
8160
8244
|
return deprecated;
|
|
8161
8245
|
}
|
|
8162
8246
|
var UPG_PROPERTY_MIGRATIONS = {
|
|
8247
|
+
"0.32.0": [
|
|
8248
|
+
// C3 — archive becomes a base-node axis. WorkspaceProperties.archived /
|
|
8249
|
+
// archived_at shipped the orthogonal-archive idea for exactly one type;
|
|
8250
|
+
// field data showed it is general (a tracker held 559 archived-completed
|
|
8251
|
+
// items beside 18 live-completed ones), so the pair moves to UPGBaseNode
|
|
8252
|
+
// and the workspace properties are @deprecated. Lift, not drop: the value
|
|
8253
|
+
// is real and its new home means the same thing.
|
|
8254
|
+
{
|
|
8255
|
+
kind: "lift_property_to_top_level",
|
|
8256
|
+
type: "workspace",
|
|
8257
|
+
from_property: "archived",
|
|
8258
|
+
to: "archived",
|
|
8259
|
+
reason: "0.32.0: archive generalised from WorkspaceProperties to UPGBaseNode.archived. Same fact, wider home; the workspace property is @deprecated."
|
|
8260
|
+
},
|
|
8261
|
+
{
|
|
8262
|
+
kind: "lift_property_to_top_level",
|
|
8263
|
+
type: "workspace",
|
|
8264
|
+
from_property: "archived_at",
|
|
8265
|
+
to: "archived_at",
|
|
8266
|
+
reason: "0.32.0: pairs with the archived lift above. UPGBaseNode.archived_at is the canonical home."
|
|
8267
|
+
},
|
|
8268
|
+
// C4 — the duplicate label surfaces. TaskProperties.labels and
|
|
8269
|
+
// BugProperties.labels were declared "applied uniformly across work item
|
|
8270
|
+
// types" and duplicated base-node `tags` with no consumer anywhere. Three
|
|
8271
|
+
// label surfaces (base tags, per-type tags, per-type labels) is two too
|
|
8272
|
+
// many; grouped labels are classification_axis + classification_value, and
|
|
8273
|
+
// ungrouped ones are `tags`.
|
|
8274
|
+
{
|
|
8275
|
+
kind: "drop_props",
|
|
8276
|
+
type: "task",
|
|
8277
|
+
drop_props: ["labels"],
|
|
8278
|
+
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."
|
|
8279
|
+
},
|
|
8280
|
+
{
|
|
8281
|
+
kind: "drop_props",
|
|
8282
|
+
type: "bug",
|
|
8283
|
+
drop_props: ["labels"],
|
|
8284
|
+
reason: "0.32.0: BugProperties.labels duplicated base-node `tags` and had no consumers. Same resolution as task."
|
|
8285
|
+
}
|
|
8286
|
+
],
|
|
8163
8287
|
// ── v0.21.0: UPG-690 D.1 — collapse residual *_status shadows into status ────
|
|
8164
8288
|
//
|
|
8165
8289
|
// Six `*_status` enum properties that re-encode their entity's own lifecycle
|
|
@@ -9295,6 +9419,48 @@ function getSplitMigrations(fromVersion, toVersion) {
|
|
|
9295
9419
|
return result;
|
|
9296
9420
|
}
|
|
9297
9421
|
var UPG_SCALAR_TO_EDGE_MIGRATIONS = {
|
|
9422
|
+
"0.32.0": [
|
|
9423
|
+
// C2 — assignment gets its own edge, superseding the 0.12.0 rules that
|
|
9424
|
+
// routed task/bug `assignee` into node_owned_by_person.
|
|
9425
|
+
//
|
|
9426
|
+
// WHY THIS IS A REVERSAL AND NOT A GAP-FILL. 0.12.0 ruled that an assignee
|
|
9427
|
+
// is an owner. Assignment has a time interval and an exclusivity that
|
|
9428
|
+
// ownership does not, and a real board ran 85% unassigned while every item
|
|
9429
|
+
// was owned — a state one edge cannot express.
|
|
9430
|
+
//
|
|
9431
|
+
// WHY EXISTING EDGES ARE NOT REWRITTEN. A node_owned_by_person edge on a
|
|
9432
|
+
// task is indistinguishable by inspection from a genuine ownership edge, so
|
|
9433
|
+
// a blanket rename would silently reclassify real facts. Measured population
|
|
9434
|
+
// across every graph in the originating workspace was ~zero, which makes the
|
|
9435
|
+
// cost of leaving them nil and the cost of guessing a wrong fact.
|
|
9436
|
+
//
|
|
9437
|
+
// user_story is included here and was MISSING from 0.12.0 entirely, though
|
|
9438
|
+
// it has carried `assignee` since 0.20.0.
|
|
9439
|
+
{
|
|
9440
|
+
from_type: "task",
|
|
9441
|
+
scalar_property: "assignee",
|
|
9442
|
+
target_type: "person",
|
|
9443
|
+
edge_type: "node_assigned_to_person",
|
|
9444
|
+
drop_scalar: false,
|
|
9445
|
+
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.'
|
|
9446
|
+
},
|
|
9447
|
+
{
|
|
9448
|
+
from_type: "bug",
|
|
9449
|
+
scalar_property: "assignee",
|
|
9450
|
+
target_type: "person",
|
|
9451
|
+
edge_type: "node_assigned_to_person",
|
|
9452
|
+
drop_scalar: false,
|
|
9453
|
+
reason: "P14 actor: same as task. Supersedes the 0.12.0 node_owned_by_person rule. Display string kept."
|
|
9454
|
+
},
|
|
9455
|
+
{
|
|
9456
|
+
from_type: "user_story",
|
|
9457
|
+
scalar_property: "assignee",
|
|
9458
|
+
target_type: "person",
|
|
9459
|
+
edge_type: "node_assigned_to_person",
|
|
9460
|
+
drop_scalar: false,
|
|
9461
|
+
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."
|
|
9462
|
+
}
|
|
9463
|
+
],
|
|
9298
9464
|
"0.12.0": [
|
|
9299
9465
|
// ── Bucket A1 — orphan scalars (no edge existed): add edge + migrate + drop ──
|
|
9300
9466
|
{
|
|
@@ -9705,22 +9871,12 @@ var UPG_SCALAR_TO_EDGE_MIGRATIONS = {
|
|
|
9705
9871
|
drop_scalar: false,
|
|
9706
9872
|
reason: "P14 actor: the release owner is a first-class person; link node_owned_by_person. Display string kept."
|
|
9707
9873
|
},
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
reason: 'P14 actor: the assignee is a first-class person ("every task assigned to this person"); link node_owned_by_person. Display string kept.'
|
|
9715
|
-
},
|
|
9716
|
-
{
|
|
9717
|
-
from_type: "bug",
|
|
9718
|
-
scalar_property: "assignee",
|
|
9719
|
-
target_type: "person",
|
|
9720
|
-
edge_type: "node_owned_by_person",
|
|
9721
|
-
drop_scalar: false,
|
|
9722
|
-
reason: "P14 actor: the assignee is a first-class person; link node_owned_by_person. Display string kept."
|
|
9723
|
-
},
|
|
9874
|
+
// NOTE (0.32.0): the task/bug `assignee` rules that lived here are SUPERSEDED
|
|
9875
|
+
// by the '0.32.0' block below, which routes them to node_assigned_to_person.
|
|
9876
|
+
// They are removed rather than left to double-run: two rules over one scalar
|
|
9877
|
+
// would mint the person once and link it twice, under two verbs that now mean
|
|
9878
|
+
// different things. Graphs promoted before 0.32.0 keep the ownership edges
|
|
9879
|
+
// they were given — see the 0.32.0 block for why those are not rewritten.
|
|
9724
9880
|
{
|
|
9725
9881
|
from_type: "roadmap",
|
|
9726
9882
|
scalar_property: "owner",
|
|
@@ -9816,6 +9972,14 @@ function getScalarToEdgeMigrations(fromVersion, toVersion) {
|
|
|
9816
9972
|
return result;
|
|
9817
9973
|
}
|
|
9818
9974
|
var UPG_EDGE_MIGRATIONS = {
|
|
9975
|
+
"0.32.0": [
|
|
9976
|
+
// C5 — the cadence scheduling edge widens from user_story to the work-item
|
|
9977
|
+
// set. No requires_target_type: the destination's target_type is the `node`
|
|
9978
|
+
// wildcard, and endpoint guards are checked against the catalog's declared
|
|
9979
|
+
// type rather than the concrete instance (same reasoning as the 0.22.0
|
|
9980
|
+
// workspace_produced_node rename below). Gate only on the unwidened source.
|
|
9981
|
+
{ 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." }
|
|
9982
|
+
],
|
|
9819
9983
|
// (target version TBD at release ceremony — pencilled at 0.22.0, the next
|
|
9820
9984
|
// breaking slot after 0.21.0 per the docket; adjust the key if Wave 4
|
|
9821
9985
|
// claims it first.) WS3 commit-provenance collapse (2026-07-05, ratified):
|
|
@@ -11369,9 +11533,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11369
11533
|
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)." },
|
|
11370
11534
|
assignee: { type: "string", description: "Assigned person. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
|
|
11371
11535
|
due_date: { type: "string", description: "ISO date due. Often tied to a release gate or SLA." },
|
|
11372
|
-
labels: { type: "string[]", description: "Free-form classification tags.
|
|
11536
|
+
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." },
|
|
11373
11537
|
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.' },
|
|
11374
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
11538
|
+
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." }
|
|
11375
11539
|
},
|
|
11376
11540
|
// BuildArtifactProperties: Build artifact.
|
|
11377
11541
|
build_artifact: {
|
|
@@ -11402,6 +11566,16 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11402
11566
|
allocated: { type: "number", description: "Capacity already allocated to work" },
|
|
11403
11567
|
available: { type: "number", description: "Remaining unallocated capacity" }
|
|
11404
11568
|
},
|
|
11569
|
+
// CaptureProperties: Capture: a dated, hashed rendition of something already in the graph.
|
|
11570
|
+
capture: {
|
|
11571
|
+
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." },
|
|
11572
|
+
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." },
|
|
11573
|
+
hash_algorithm: { type: "string", enum: ["sha256", "sha1", "md5"], description: "Hash algorithm. Absent means `sha256`." },
|
|
11574
|
+
captured_at: { type: "string", description: "ISO timestamp the capture was taken." },
|
|
11575
|
+
media_type: { type: "string", description: 'IANA media type of the bytes. @example "image/png"' },
|
|
11576
|
+
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." },
|
|
11577
|
+
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." }
|
|
11578
|
+
},
|
|
11405
11579
|
// CeremonyProperties: Ceremony entity.
|
|
11406
11580
|
ceremony: {
|
|
11407
11581
|
ceremony_type: { type: "string", enum: ["standup", "planning", "review", "retro", "sync", "demo", "other"], description: "Kind of recurring meeting" },
|
|
@@ -11605,7 +11779,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11605
11779
|
members: { type: "object[]", description: "The frozen member arrangement, captured at publish. Layout and pointers only, never resolved content." },
|
|
11606
11780
|
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." },
|
|
11607
11781
|
published_at: { type: "string", description: "ISO timestamp of the most recent publish or republish." },
|
|
11608
|
-
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
11782
|
+
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." },
|
|
11783
|
+
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." },
|
|
11784
|
+
presentation: { type: "object", description: "Advisory rendering intent for the composition as a whole. A consumer may ignore it entirely and still be conformant." }
|
|
11609
11785
|
},
|
|
11610
11786
|
// ConfigurationAxisProperties: A named dimension along which the product's composition differs.
|
|
11611
11787
|
configuration_axis: {
|
|
@@ -12062,7 +12238,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12062
12238
|
start_date: { type: "string", description: "ISO date work begins" },
|
|
12063
12239
|
target_date: { type: "string", description: "ISO date work completes" },
|
|
12064
12240
|
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.' },
|
|
12065
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
12241
|
+
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." }
|
|
12066
12242
|
},
|
|
12067
12243
|
// ErrorBudgetProperties: Error budget.
|
|
12068
12244
|
error_budget: {
|
|
@@ -12246,7 +12422,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12246
12422
|
target_date: { type: "string", description: "ISO date work completes" },
|
|
12247
12423
|
health: { type: "string", enum: ["on_track", "at_risk", "off_track"], description: "Delivery health" },
|
|
12248
12424
|
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.' },
|
|
12249
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
12425
|
+
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." }
|
|
12250
12426
|
},
|
|
12251
12427
|
// FeatureAreaProperties: A structural grouping of related features within a product.
|
|
12252
12428
|
feature_area: {
|
|
@@ -13242,7 +13418,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13242
13418
|
url: { type: "string", description: "Where the product lives. Marketing site, app store URL, etc.", modifier: "volatile" },
|
|
13243
13419
|
logo_url: { type: "string", description: "Logo or icon URL. Used to render product cards and lists.", modifier: "volatile" },
|
|
13244
13420
|
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" },
|
|
13245
|
-
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." }
|
|
13421
|
+
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." },
|
|
13422
|
+
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.' }
|
|
13246
13423
|
},
|
|
13247
13424
|
// ProductAreaProperties: ProductArea entity.
|
|
13248
13425
|
product_area: {
|
|
@@ -13842,9 +14019,9 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13842
14019
|
effort: { type: "string", description: 'Effort estimate (e.g. "2h", "1d", "3 points"). Use a consistent unit within your team.' },
|
|
13843
14020
|
priority: { type: "string", enum: ["urgent", "high", "medium", "low", "none"], description: "Relative importance against other tasks" },
|
|
13844
14021
|
due_date: { type: "string", description: "ISO date due. Typically bounded by the containing story's due date." },
|
|
13845
|
-
labels: { type: "string[]", description: "Free-form classification tags.
|
|
14022
|
+
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." },
|
|
13846
14023
|
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.' },
|
|
13847
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
14024
|
+
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." }
|
|
13848
14025
|
},
|
|
13849
14026
|
// TeamProperties: Team entity.
|
|
13850
14027
|
team: {
|
|
@@ -14066,7 +14243,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14066
14243
|
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." },
|
|
14067
14244
|
due_date: { type: "string", description: "ISO date due. Typically bounded by the release or planning_cycle the story is scheduled into." },
|
|
14068
14245
|
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.' },
|
|
14069
|
-
workflow_state_category: { type: "string", description: 'Canonical bucket the raw `workflow_state` maps onto for reasoning
|
|
14246
|
+
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." }
|
|
14070
14247
|
},
|
|
14071
14248
|
// ValueObjectProperties: DDD value object.
|
|
14072
14249
|
value_object: {
|
|
@@ -14181,8 +14358,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14181
14358
|
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." },
|
|
14182
14359
|
owner: { type: "string", description: "Workspace owner (handle or email). Display label; canonical owner is `team_owns_workspace` or `persona_owns_workspace`." },
|
|
14183
14360
|
member_count: { type: "number", description: "Snapshot count. `team_works_in_workspace` edges are the source of truth.", modifier: "derived" },
|
|
14184
|
-
archived: { type: "boolean", description: "Archived. Archived workspaces remain queryable but hidden from default views." },
|
|
14185
|
-
archived_at: { type: "string", description: "ISO timestamp archived. Pairs with `archived === true`." },
|
|
14361
|
+
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." },
|
|
14362
|
+
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']`." },
|
|
14186
14363
|
icon: { type: "string", description: "Display icon (emoji or icon name)" },
|
|
14187
14364
|
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." },
|
|
14188
14365
|
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." }
|
|
@@ -21586,6 +21763,7 @@ var ENTITY_EMOJI = {
|
|
|
21586
21763
|
competitor_signal: "\u{1F4E1}",
|
|
21587
21764
|
compliance_framework: "\u{1F3DF}\uFE0F",
|
|
21588
21765
|
compliance_requirement: "\u26F2",
|
|
21766
|
+
capture: "\u{1F4F8}",
|
|
21589
21767
|
composition: "\u{1F5BC}\uFE0F",
|
|
21590
21768
|
constraint: "\u{1F512}",
|
|
21591
21769
|
contact: "\u{1F4C7}",
|
|
@@ -24965,9 +25143,9 @@ var PRODUCT_SPEC_GUIDE = {
|
|
|
24965
25143
|
},
|
|
24966
25144
|
{
|
|
24967
25145
|
name: "Planning Cadence",
|
|
24968
|
-
description: "The cadence axis. Create a planning_cycle (a sprint, iteration, quarter, or PI), nest finer cycles inside a coarse one, schedule the
|
|
24969
|
-
entity_types: ["planning_cycle", "user_story", "objective"],
|
|
24970
|
-
edge_chain: ["planning_cycle_contains_planning_cycle", "
|
|
25146
|
+
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.",
|
|
25147
|
+
entity_types: ["planning_cycle", "task", "user_story", "objective"],
|
|
25148
|
+
edge_chain: ["planning_cycle_contains_planning_cycle", "planning_cycle_schedules_work_item", "objective_scoped_to_planning_cycle"]
|
|
24971
25149
|
}
|
|
24972
25150
|
],
|
|
24973
25151
|
required_bridges: [
|
|
@@ -25664,7 +25842,7 @@ var PORTFOLIO_GUIDE = {
|
|
|
25664
25842
|
var WORKSPACE_GUIDE = {
|
|
25665
25843
|
domain_id: "workspace",
|
|
25666
25844
|
anchor_entity: "workspace",
|
|
25667
|
-
creation_sequence: ["workspace", "framework_exercise", "composition"],
|
|
25845
|
+
creation_sequence: ["workspace", "framework_exercise", "composition", "capture"],
|
|
25668
25846
|
patterns: [
|
|
25669
25847
|
{
|
|
25670
25848
|
name: "Canvas to published view",
|
|
@@ -28278,7 +28456,7 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28278
28456
|
{
|
|
28279
28457
|
id: "planning-cycle-without-scheduled-work",
|
|
28280
28458
|
name: "Planning cycle with no scheduled work",
|
|
28281
|
-
description: "A planning_cycle exists but neither schedules any
|
|
28459
|
+
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.",
|
|
28282
28460
|
structured_condition: {
|
|
28283
28461
|
operator: "and",
|
|
28284
28462
|
checks: [
|
|
@@ -28287,8 +28465,8 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28287
28465
|
check: {
|
|
28288
28466
|
type: "relationship",
|
|
28289
28467
|
source_type: "planning_cycle",
|
|
28290
|
-
edge_type: "
|
|
28291
|
-
target_type: "
|
|
28468
|
+
edge_type: "planning_cycle_schedules_work_item",
|
|
28469
|
+
target_type: "node",
|
|
28292
28470
|
comparison: "not_exists"
|
|
28293
28471
|
}
|
|
28294
28472
|
},
|
|
@@ -28304,10 +28482,16 @@ var UPG_ANTI_PATTERNS = [
|
|
|
28304
28482
|
]
|
|
28305
28483
|
},
|
|
28306
28484
|
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.",
|
|
28307
|
-
remediation: "Schedule the
|
|
28485
|
+
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.",
|
|
28308
28486
|
stages: ["build", "beta", "launch", "growth", "mature"],
|
|
28309
28487
|
severity: "low",
|
|
28310
28488
|
since: "0.20.0"
|
|
28489
|
+
// RETARGETED at 0.32.0 from planning_cycle_schedules_user_story. Left
|
|
28490
|
+
// unchanged it would have become a false-positive generator the moment
|
|
28491
|
+
// cycles could hold tasks: a cycle full of scheduled tasks and no stories
|
|
28492
|
+
// would have reported as empty. Labeled fixtures cover the near-miss this
|
|
28493
|
+
// is most likely to get wrong — a coarse `period` cycle that legitimately
|
|
28494
|
+
// holds only sub-cycles and schedules nothing directly.
|
|
28311
28495
|
},
|
|
28312
28496
|
// ── Market intelligence layer ───────────────────────────────────────────
|
|
28313
28497
|
{
|
|
@@ -30433,7 +30617,7 @@ var UPG_REGIONS = [
|
|
|
30433
30617
|
"roadmap_item_references_feature",
|
|
30434
30618
|
"feature_request_voted_on_by_feedback_vote",
|
|
30435
30619
|
"planning_cycle_contains_planning_cycle",
|
|
30436
|
-
"
|
|
30620
|
+
"planning_cycle_schedules_work_item"
|
|
30437
30621
|
],
|
|
30438
30622
|
boundary_edges: [
|
|
30439
30623
|
{
|
|
@@ -32361,9 +32545,12 @@ var NODE_KEY_ORDER = [
|
|
|
32361
32545
|
"title",
|
|
32362
32546
|
"slug",
|
|
32363
32547
|
"aliases",
|
|
32548
|
+
"key",
|
|
32364
32549
|
"description",
|
|
32365
32550
|
"tags",
|
|
32366
32551
|
"status",
|
|
32552
|
+
"archived",
|
|
32553
|
+
"archived_at",
|
|
32367
32554
|
"lifecycle_status",
|
|
32368
32555
|
"source_id",
|
|
32369
32556
|
"source_type",
|
|
@@ -32653,7 +32840,7 @@ function checkHeaderSealText(text) {
|
|
|
32653
32840
|
}
|
|
32654
32841
|
|
|
32655
32842
|
// src/index.ts
|
|
32656
|
-
var UPG_VERSION = "0.
|
|
32843
|
+
var UPG_VERSION = "0.32.0";
|
|
32657
32844
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
32658
32845
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
32659
32846
|
var UPG_TYPES = getTypes();
|