@unified-product-graph/core 0.32.2 → 0.33.1
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 +53 -0
- package/README.md +1 -1
- package/dist/index.cjs +179 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +565 -71
- package/dist/index.d.ts +565 -71
- package/dist/index.js +178 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,59 @@ This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.33.1] - 2026-08-22
|
|
11
|
+
|
|
12
|
+
Patch, docs only. No schema change: no entity, edge, property, enum, lifecycle or tool added, removed or altered. One normative contradiction, published in 0.33.0, is resolved.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`UPGBaseNode.key`'s `@remarks` no longer contradicts itself.** The `WHICH PREFIX` paragraph ended *"A product where nothing declares a prefix mints no keys"*, which deletes the inferred rung. Two paragraphs later, in the SAME block, the portfolio-uniqueness note names *"the quiet case where nobody declares a prefix and it is inferred from existing keys"*, and the product-scoped minting rule applies itself *"on the MINT path including a prefix that was INFERRED from existing keys rather than requested by any caller"*. `team.key_prefix` rule 3 presupposes the same rung: a refusal described as covering the inferred case is meaningless if inference never mints. The field settles which sentence is true — the only keyed graph in the estate carries 1,032 keys minted by inference in a product that declares no prefix at all, and under the deleted rung that graph could not exist. The paragraph now states the ladder as built, in four rungs: a create names its prefix explicitly; otherwise the prefixes declared by the product's teams are the candidates; otherwise the prefixes observed on the product's existing keys are; a product with none of the three mints no keys. `team.key_prefix` is untouched and was already correct.
|
|
16
|
+
|
|
17
|
+
### Notes
|
|
18
|
+
- **Truth is unmoved: 324 entities · 1084 edges · 69 cross-edges · 25 anti-patterns · 98 tools · 25 polymorphic keys in 14 families.** Prose only, so nothing moves a count, a migration, a fixture or an editorial fingerprint. That last one is worth stating rather than assuming: `check:editorial` excludes prose by design, so the most consequential text change a release can make is invisible to the gate that exists to catch documentation going stale.
|
|
19
|
+
- **What this patch deliberately does NOT change.** `team.key_prefix`'s suppression paragraph makes a declared team prefix replace the candidate set outright. Applied to an OBSERVED prefix that no team has claimed, that instruction is a live hazard: in a graph carrying two observed prefixes and no declaration, one team declaring one of them collapses the candidate set to that one and every later create mints under it silently, including work belonging to the other. Narrowing it is a change to what implementations must do rather than a correction to what they were told, so it is not smuggled into a patch. Interim guidance for anyone minting today: declare every prefix a product uses, or declare none.
|
|
20
|
+
|
|
21
|
+
## [0.33.0] - 2026-08-22
|
|
22
|
+
|
|
23
|
+
Minor, additive. Zero new entity types, zero new tools, zero new checks. The through-line: 0.32.0 gave the graph the right shapes and left three of them unreachable, and most of this release is reach rather than shape.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **`project_delivers_work_item`**, widening and renaming `project_delivers_epic` to the polymorphic work-item endpoint (`semantic`, `deliberate_only`). A tracker dry-run carried 651 project memberships as a vendor property and emitted zero edges, because the default issue type an adapter produces is `task` and the only project edge could not reach it. The verb stays `delivers` and NOT `contains`: a containment verb obliges a `UPG_VALID_CHILDREN` pair, and that map is keyed by concrete type names, so a wildcard endpoint can never discharge the obligation its own verb creates. `UPG_VALID_CHILDREN.project` is untouched, deliberately. A work item is contained once, by its epic or feature, and referenced many times. One rename migration; measured population one, in the CI fixture.
|
|
27
|
+
- **`external_links?: UPGExternalLink[]` on `UPGBaseNode`**, generalising the `ServiceProperties.links` shape. `external_ref` still names THE canonical artifact; this holds everything else. 162 measured links were overflowing into an undeclared vendor bag key. Named `external_links` rather than `external_refs` because the latter is one character from `external_ref` and means the opposite thing.
|
|
28
|
+
- **`created_at` and `updated_at` on `UPGBaseNode`, tagged `@volatile`.** Store metadata, declared so a view query can open a window over them. Two of the six date dimensions a real board filters on are these, and leaving them undeclared would ship a query language that cannot say what the surface most often says.
|
|
29
|
+
- **`clauses?: UPGViewClause[]` on `UPGViewQuery`**, with `UPGViewDimension`, `UPGTimeWindow` and `UPGViewEdgeClause`. One clause list carrying per-clause negation, declared time windows and edge conditions, none of which the named fields can hold. The named fields stay as the positive-only shorthand and are NOT deprecated; the precedence rule is stated: a reader that finds `clauses` uses it and ignores the named fields. A window is evaluated at read time, never frozen at save, so a saved view that says "this quarter" means this quarter to whoever opens it. The team cadence is deliberately NOT a window: it resolves through the active-cycle designation as an edge clause, because field data holds 19 cycles of which every one is dateless and exactly one is `active`.
|
|
30
|
+
- **The D/E addendum (ratified 2026-08-22), three arms on the shapes this release mints.** **`target_designation?: 'viewer'`** on `UPGViewEdgeClause`: the person axis' parallel to the cadence axis' `target_status: ['active']`, naming the reader by role and resolving in their session. A saved view meaning "assigned to me" cannot store an id, because the id it stores is one particular person and the view is then permanently about a colleague; a shipped surface reached for the sentinel `'@me'` inside `target_ids`, which round-trips and whose meaning does not travel. A sentinel in an id field is a private protocol wearing a public shape. **`from_focus.depth` gains `'unbounded'`**: a relative selection over a tree is transitive by nature and has no correct finite depth, so a surface with only a number picks a big one, and a shipped one picked 64, which is indistinguishable from a caller who meant 64 and silently truncates the first deeper graph. **`layout` gains `'tree'` and an advisory `nest_by?: string[]`**: `group_by` partitions a flat set on a value and cannot express nesting, because a tree's levels are edges rather than property values.
|
|
31
|
+
- **`UPGQueryDrivenLayer`**, extended by both `composition` and `workspace`. A layer is query-driven while it is being worked on, not only after it is published; declaring the query only on the published half made the fact something invented at publish time.
|
|
32
|
+
- **`person_member_of_team`** and **`person_reports_to_person`**, both `semantic`, both sourced on `person`. The org half of the ontology had 17 edges reaching `team` and no path from a person to any of them, and `person_reports_to_person` was cited three times in spec prose while existing zero times, once in an anti-pattern entry that instructed users toward an edge the catalog would refuse. Neither is a reversal of the containment-free stance on `person`, which forecloses HIERARCHY edges added to satisfy a hierarchy audit; both are reference edges, and sourcing on `person` makes the containment reading unavailable rather than merely unchosen. Membership is unqualified: `role` is already an entity type, and a role string on the edge would be a second role model beside it.
|
|
33
|
+
- **`team.key_prefix`.** The prefix names a team, which `product.key_prefix`' own summary already said, and a single string could never express a product with two of them.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- **`agent_skill` and `agent_hook` move from `WORK_ITEM` to `OPERATIONAL`.** A durable capability and an event trigger were both reaching `in_review` and `done`, and zero of ten field instances had ever left the initial phase. Their nearest siblings `monitor` and `alert_rule` already run OPERATIONAL. Neither becomes lifecycle-free: the `user_story` precedent for that needs a paired lifecycle-bearing entity and there is none. A status migration remaps the WORK_ITEM vocabulary; measured population four, all `todo`.
|
|
37
|
+
- **`specification`'s type summary** is corrected. See Fixed.
|
|
38
|
+
- **`department`'s type summary** records the division ruling: division, org unit and business unit are alternative LABELS for this type, not separate types. That ruling has been right and one line deep in a label array since 0.1.0, and it had already produced two phantom entity types in a briefing. The flat-department constraint is stated with it.
|
|
39
|
+
- **`UPGBaseNode.properties`** documents the namespaced-key rule, extended from the canvas bag to the node bag: a key a tool owns and the spec does not declare is written `<tool>:<key>`. An underscore key is indistinguishable from a misspelled spec property, which no migration can target and no validator can flag. One measured undeclared extension, raw tracker state history on 1,032 nodes, is recorded as DELIBERATELY undeclared with its pull-forward condition, because silence and a decision look identical six months later.
|
|
40
|
+
- **`NODE_KEY_ORDER` is exported**, with a test asserting every base field appears in it. A subset assertion, never equality: two tolerated non-base keys belong there and an equality check would push the next author to delete them.
|
|
41
|
+
- **MCP `create_node` / `batch_create_nodes` / `update_node` / `batch_update_nodes`** accept `archived` and `archived_at`; the create pair also accepts `key`. This mirrors `graph-service`'s existing policy exactly rather than inventing one. Widening the JSON schema alone does nothing, which is the trap this item exists to name: the SDK hand-builds its patch from its own arg interfaces, so an argument arriving without a matching field is never copied. All three layers moved.
|
|
42
|
+
- **`get_import_recipe` serves per-source read caveats** on both the curated and the scaffold path, keyed independently of whether a curated table exists. Linear has no curated recipe and two of the sharpest hazards in the corpus: `list_issues` truncates descriptions mid-markup on 913 of 1,032 issues, and an over-complex query returns HTTP 200 with an error body. Both are one class, a failure that looks like a success.
|
|
43
|
+
|
|
44
|
+
### Deprecated
|
|
45
|
+
- **`objective.timeframe`** (`removeIn` 1.0.0), pointing at `objective_scoped_to_planning_cycle`, which has existed since 0.20.0. **`strategic_theme.time_horizon`** gains the same `removeIn`; it was tagged at 0.20.0 with no removal date and survived twelve minors, which is what an undated deprecation buys. Planning entities relate to time through the scheduling edge and free-text time fields retire, and the class has a stated BOUNDARY so the next author applies a test rather than a slogan: the class is the entities that can be scheduled in a planning cycle, which is these two. `strategic_pillar.time_horizon`, `vision.timeframe`, `roadmap.timeframe`, `roadmap_item.quarter` and `journey_phase.timeframe` are named exemptions. No `drop_props`: dropping the string before the cycle exists destroys the only record of the intent.
|
|
46
|
+
- **`product.key_prefix`** (`removeIn` 1.0.0), superseded by `team.key_prefix`. They are not a default and an override, which describe one fact at two scopes; they describe different facts, and the product field is correct only when the product has exactly one team. Retained as the single-team unasked default and ignored whenever any team declares a prefix, because removal now would strand every graph that has a product prefix and no `team` nodes.
|
|
47
|
+
- **`agent_hook.hook_status`** (`removeIn` 1.0.0), a `*_status` shadow of the base `status` and the class Pattern D collapsed fourteen times at 0.15.0. `error` maps to `paused`: OPERATIONAL has no failure phase, and runtime health is store state rather than a fact about the thing.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- **`specification`'s summary no longer implies a gap that does not exist, and the three edge names an editorial sweep called fabricated are not fabricated.** `UPG_CROSS_ONLY_EDGE_TYPES` has carried `product_implements_specification`, `product_exposes_specification` and `feature_conforms_to_specification` since 0.9.12: product-to-foundation links are portfolio cross-edges by design, because a governed specification is a registry canonical. A citation check that reads only the edge catalog reports every legitimate cross-only edge as a phantom. The gap that DOES remain is stated with its pull-forward condition: a `specification` node inside a single product graph, with no portfolio around it, has no conformance edge available.
|
|
51
|
+
- The workspace archive read tolerance from 0.32.0 retires on schedule; the writer had already flipped, so it was carrying nothing still produced.
|
|
52
|
+
- An orphaned doc comment on `AgentSkillProperties` that documented no field.
|
|
53
|
+
|
|
54
|
+
### Notes
|
|
55
|
+
- Truth: **324 entities · 1084 edges · 69 cross-edges · 25 anti-patterns · 98 tools · 25 polymorphic keys in 14 families**. The lifecycle-phase count that earlier entries carry is deliberately NOT restated here: it is the one figure in that line no check asserts, and it does not reproduce. Counted off the built catalog it is 869 phase rows across 193 lifecycles, or 342 distinct (template, phase) pairs; neither is the 334 carried since 0.32.0, which traces to a planning document rather than to a constant. A number nobody can derive should not ship in a release note, so it is dropped rather than repeated. Wiring a phase count into `check:count-drift` is banked with its condition already met; it is not done in this release because adding a check would change the shape of a release ratified as adding none. No entity minted; four edges added net of one rename; cross-edges unchanged because `product`, `feature` and `person` are not `portfolio_shared`, so nothing here can be eligible.
|
|
56
|
+
- **Portfolio-wide key uniqueness is now DEFINED AND UNENFORCED**, stated on `UPGBaseNode.key`. Within a portfolio one `(prefix, number)` should identify one node; uniqueness is enforced per product, so two products minting under one prefix produce the same citation for two different things. This is measured, not feared: a fixture reproduces the collision through the ordinary create path, including the quiet case where nobody declares a prefix and it is inferred. No per-product check can ever see it, because each product reports its own key as valid. The standing audit is deferred with a dated condition: the first portfolio holding two keyed products, which is also the first moment a real near-miss corpus can be sampled instead of invented.
|
|
57
|
+
- The key-prefix PRECEDENCE is ruled in the spec rather than left to an application ladder: a declared team prefix supersedes the product prefix, and a declared prefix is a candidate before it is observed. Leaving precedence to the ladder would make migration ORDER decide behaviour, per graph, silently.
|
|
58
|
+
- The MCP server may not MINT a key, only accept one. Deriving `max(existing)+1` needs paged reads, and a second unpaged minter is a duplicate-key generator.
|
|
59
|
+
- Banked with conditions: a `rename_bag_key` migration kind; generic patch derivation from `UPG_BASE_NODE_FIELDS` in the SDK; re-classifying `product_conforms_to_a11y_standard` from `hierarchy` to `semantic`; wiring a phase count into `check:count-drift`; `person_holds_role`; the within-graph conformance edge.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
10
63
|
## [0.32.2] - 2026-08-21
|
|
11
64
|
|
|
12
65
|
Patch. No schema change: no entity, edge, property, enum, or lifecycle added or removed. The 0.32.0 base fields become reachable through the write path that was supposed to carry them.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
The TypeScript core for the **Unified Product Graph (UPG)**, an open specification that gives product tools a shared vocabulary for entities, edges, and frameworks.
|
|
9
9
|
|
|
10
|
-
324 entity types across 37 domains.
|
|
10
|
+
324 entity types across 37 domains. 1084 typed edges. 53 canonical framework definitions, with more added as each is reviewed.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ __export(index_exports, {
|
|
|
51
51
|
LEGACY_PRODUCT_STAGES: () => LEGACY_PRODUCT_STAGES,
|
|
52
52
|
MARKDOWN_FORMAT_VERSION: () => MARKDOWN_FORMAT_VERSION,
|
|
53
53
|
MARKET_COMPETITIVE_PLAYBOOK: () => MARKET_COMPETITIVE_PLAYBOOK,
|
|
54
|
+
NODE_KEY_ORDER: () => NODE_KEY_ORDER,
|
|
54
55
|
OPERATIONS_QUALITY_PLAYBOOK: () => OPERATIONS_QUALITY_PLAYBOOK,
|
|
55
56
|
PRESENT_UNDER_PROPERTY: () => PRESENT_UNDER_PROPERTY,
|
|
56
57
|
PRODUCT_DELIVERY_PLAYBOOK: () => PRODUCT_DELIVERY_PLAYBOOK,
|
|
@@ -2726,6 +2727,53 @@ var UPG_EDGE_CATALOG = {
|
|
|
2726
2727
|
// contained by product / department / team. Hierarchy edges for person
|
|
2727
2728
|
// are deliberately omitted; adding them only to satisfy a hierarchy
|
|
2728
2729
|
// audit would invert the orthogonality the type was introduced to express.
|
|
2730
|
+
// ── Org membership and reporting (0.33.0) ───────────────────────────────────
|
|
2731
|
+
// Both edges are `semantic` REFERENCE edges and neither is a reversal of
|
|
2732
|
+
// the containment-free stance recorded immediately above. That note forecloses
|
|
2733
|
+
// HIERARCHY edges added ONLY to satisfy a hierarchy audit; these are neither.
|
|
2734
|
+
// UPG_CONTAINMENT_FREE_TYPES, UPG_VALID_CHILDREN, get_tree and G2b are all
|
|
2735
|
+
// untouched, and node_assigned_to_person was minted cross-domain onto `person`
|
|
2736
|
+
// at 0.32.0 on the same reading.
|
|
2737
|
+
//
|
|
2738
|
+
// DIRECTION IS LOAD-BEARING, not a preference. get_tree reads SOURCE as parent
|
|
2739
|
+
// (see the team_contains_team note above), so a team -> person edge would sit
|
|
2740
|
+
// one classification change away from putting people in the org tree. Sourcing
|
|
2741
|
+
// on `person` makes the containment reading unavailable rather than merely
|
|
2742
|
+
// unchosen. `team_includes_person` classified hierarchy is the shape to refuse.
|
|
2743
|
+
//
|
|
2744
|
+
// CLASSIFICATION IS `semantic` AND NOT `cross-domain`, which the 0.33.0 design
|
|
2745
|
+
// recommended before the guardrail was consulted. `person` and `team` both live
|
|
2746
|
+
// in the team_org domain, so a `cross-domain` label would contradict itself: the
|
|
2747
|
+
// edge crosses no domain boundary, and T1.7 forbids NEW same-domain cross-domain
|
|
2748
|
+
// edges precisely so that debt stops growing. Nothing the design argument
|
|
2749
|
+
// depends on moves: `semantic` enters neither UPG_VALID_CHILDREN nor get_tree,
|
|
2750
|
+
// and G2b is not engaged.
|
|
2751
|
+
// Membership is UNQUALIFIED: no role property on the edge. `role` is already an
|
|
2752
|
+
// entity type with team_staffed_with_role, so a role string here would be a
|
|
2753
|
+
// second role model beside a node type. The consequence is stated rather than
|
|
2754
|
+
// hidden: the graph can say a team has a slot and that a person is on the team,
|
|
2755
|
+
// and not that the person fills the slot. `person_holds_role` is banked, with
|
|
2756
|
+
// its condition the first field graph carrying both where the join is asked for.
|
|
2757
|
+
//
|
|
2758
|
+
// Neither is cross_product_eligible: `person` is not portfolio_shared, so the
|
|
2759
|
+
// 0.18.0 both-endpoints gate rejects the flag regardless of any team-scoping
|
|
2760
|
+
// ruling.
|
|
2761
|
+
person_member_of_team: { forward_verb: "member_of", reverse_verb: "has_member", classification: "semantic", source_type: "person", target_type: "team" },
|
|
2762
|
+
// Cited three times in spec source since 0.17.2 and existing zero times: the
|
|
2763
|
+
// team_contains_team note above, the team-nesting domain guide, and an
|
|
2764
|
+
// anti_patterns entry that instructs users to model a cross-department
|
|
2765
|
+
// reporting line with this edge. An anti-pattern guide is instructional by
|
|
2766
|
+
// construction, so that third site was telling users to use an edge the catalog
|
|
2767
|
+
// would refuse. Minting makes all three true and needs no prose edit.
|
|
2768
|
+
//
|
|
2769
|
+
// semantic, not hierarchy, and the temptation is real: a reporting line IS a
|
|
2770
|
+
// hierarchy in ordinary English, which is exactly why classifying it that way
|
|
2771
|
+
// would put `person` into containment and become the reversal the sibling edge
|
|
2772
|
+
// above is shaped to avoid. The org chart is a reference structure over
|
|
2773
|
+
// containment-free people. Same classification as the membership edge above, so
|
|
2774
|
+
// the person / team / department triangle is decided once rather than three
|
|
2775
|
+
// times.
|
|
2776
|
+
person_reports_to_person: { forward_verb: "reports_to", reverse_verb: "has_report", classification: "semantic", source_type: "person", target_type: "person" },
|
|
2729
2777
|
team_staffed_with_role: { forward_verb: "staffed_with", reverse_verb: "staffed_in", classification: "hierarchy", source_type: "team", target_type: "role" },
|
|
2730
2778
|
team_targets_team_okr: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "hierarchy", source_type: "team", target_type: "team_okr" },
|
|
2731
2779
|
team_reflects_in_retrospective: { forward_verb: "reflects_in", reverse_verb: "reflects", classification: "hierarchy", source_type: "team", target_type: "retrospective" },
|
|
@@ -2770,7 +2818,31 @@ var UPG_EDGE_CATALOG = {
|
|
|
2770
2818
|
program_reported_via_status_report: { forward_verb: "reported_via", reverse_verb: "reports", classification: "hierarchy", source_type: "program", target_type: "status_report" },
|
|
2771
2819
|
program_contains_epic: { forward_verb: "contains", reverse_verb: "contained_in", classification: "cross-domain", source_type: "program", target_type: "epic" },
|
|
2772
2820
|
project_implements_initiative: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "cross-domain", source_type: "project", target_type: "initiative" },
|
|
2773
|
-
|
|
2821
|
+
// WIDENED AT 0.33.0, renamed from project_delivers_epic. The epic-only
|
|
2822
|
+
// endpoint could not hold a real tracker import: a Linear dry-run carried 651
|
|
2823
|
+
// project memberships as `properties.linear_project_id` and emitted zero
|
|
2824
|
+
// edges, because the default issue type an adapter produces is `task` and the
|
|
2825
|
+
// only project edge could not reach it. Endpoint-polymorphic over the
|
|
2826
|
+
// work-item set {feature, epic, user_story, task, bug} by the same
|
|
2827
|
+
// construction as planning_cycle_schedules_work_item: the `work_item` token
|
|
2828
|
+
// names the intended semantic domain, the endpoint is the `node` wildcard.
|
|
2829
|
+
//
|
|
2830
|
+
// NOT a `contains` verb, and UPG_VALID_CHILDREN.project is deliberately
|
|
2831
|
+
// untouched. `contains` is this catalog's containment verb and every edge
|
|
2832
|
+
// using it is classification: 'hierarchy', which under G2b obliges a matching
|
|
2833
|
+
// UPG_VALID_CHILDREN pair. That map is Record<parent, child[]> of concrete
|
|
2834
|
+
// type names, so a polymorphic endpoint can never discharge the obligation
|
|
2835
|
+
// its own verb would create. A work item is CONTAINED once (by its epic or
|
|
2836
|
+
// feature) and REFERENCED many times; the parent axis is containment and the
|
|
2837
|
+
// project axis is a reference. Giving project a second containment claim over
|
|
2838
|
+
// the same task is the two-trees problem.
|
|
2839
|
+
//
|
|
2840
|
+
// classification moves cross-domain -> semantic with the widening: pinned to
|
|
2841
|
+
// project -> epic it was a cross-domain pair, and once the endpoint is the
|
|
2842
|
+
// wildcard it is a reference relation between a planning container and
|
|
2843
|
+
// arbitrary work. deliberate_only because project membership is authored,
|
|
2844
|
+
// never inferred from co-occurrence.
|
|
2845
|
+
project_delivers_work_item: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "semantic", source_type: "project", target_type: "node", deliberate_only: true },
|
|
2774
2846
|
milestone_gates_release: { forward_verb: "gates", reverse_verb: "gated_by", classification: "cross-domain", source_type: "milestone", target_type: "release" },
|
|
2775
2847
|
milestone_triggers_release: { forward_verb: "triggers", reverse_verb: "triggered_by", classification: "cross-domain", source_type: "milestone", target_type: "release" },
|
|
2776
2848
|
deliverable_ships_feature: { forward_verb: "ships", reverse_verb: "shipped_by", classification: "cross-domain", source_type: "deliverable", target_type: "feature" },
|
|
@@ -3836,6 +3908,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
3836
3908
|
// so the lifecycle is governed rather than a standalone root (0.12.1 refinement).
|
|
3837
3909
|
operating_lifecycle_defined_by_specification: { forward_verb: "defined_by", reverse_verb: "defines", classification: "semantic", source_type: "operating_lifecycle", target_type: "specification" },
|
|
3838
3910
|
operating_lifecycle_contains_operating_stage: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "operating_lifecycle", target_type: "operating_stage" },
|
|
3911
|
+
// Conformance (0.33.0, RE-OPENED). 0.33.0 planned to mint
|
|
3912
|
+
// product_conforms_to_specification and feature_conforms_to_specification here.
|
|
3913
|
+
// It does not, and the reason is a fact the design missed: the product-to-
|
|
3914
|
+
// foundation links are NOT absent, they live one tier up. See
|
|
3915
|
+
// UPG_CROSS_ONLY_EDGE_TYPES in shapes/document.ts, which has carried
|
|
3916
|
+
// `product_implements_specification`, `product_exposes_specification` and
|
|
3917
|
+
// `feature_conforms_to_specification` as portfolio-native cross-product edges
|
|
3918
|
+
// since 0.9.12, exactly as the section comment above says. The tiers are
|
|
3919
|
+
// disjoint by construction, so minting the same key here is a hard collision,
|
|
3920
|
+
// and minting only the product half would split one question across two tiers
|
|
3921
|
+
// under two near-synonymous names.
|
|
3922
|
+
//
|
|
3923
|
+
// What survives is the real gap, stated here rather than closed: a
|
|
3924
|
+
// `specification` node held INSIDE a single product graph, with no portfolio,
|
|
3925
|
+
// has no conformance edge, because the cross-only tier needs a registry target.
|
|
3926
|
+
// Whether that case is closed by widening the tier or by a catalog edge under a
|
|
3927
|
+
// non-colliding key is a design question and is banked, with its condition the
|
|
3928
|
+
// first single-product graph that models a specification it conforms to.
|
|
3839
3929
|
// Stewardship: a registry specification is governed by a (registry-hostable) organization (0.9.13).
|
|
3840
3930
|
specification_governed_by_organization: { forward_verb: "governed_by", reverse_verb: "governs", classification: "semantic", source_type: "specification", target_type: "organization" },
|
|
3841
3931
|
// Operating-lifecycle cross-edges (0.11.6): a product's journey phase realises a canonical
|
|
@@ -4050,7 +4140,11 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
|
4050
4140
|
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
4051
4141
|
// bug}. The `work_item` key token names the intended semantic domain; the
|
|
4052
4142
|
// endpoint is the `node` wildcard.
|
|
4053
|
-
"planning_cycle_schedules_work_item"
|
|
4143
|
+
"planning_cycle_schedules_work_item",
|
|
4144
|
+
// Project membership (0.33.0, widened from project_delivers_epic):
|
|
4145
|
+
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
4146
|
+
// bug}. Same construction and same reasoning as the cadence edge above.
|
|
4147
|
+
"project_delivers_work_item"
|
|
4054
4148
|
];
|
|
4055
4149
|
var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
|
|
4056
4150
|
function isPolymorphicEdge(key) {
|
|
@@ -4107,6 +4201,9 @@ var BASE_NODE_FIELD_PRESENCE = {
|
|
|
4107
4201
|
external_tool: true,
|
|
4108
4202
|
external_ref: true,
|
|
4109
4203
|
external_id: true,
|
|
4204
|
+
external_links: true,
|
|
4205
|
+
created_at: true,
|
|
4206
|
+
updated_at: true,
|
|
4110
4207
|
properties: true
|
|
4111
4208
|
};
|
|
4112
4209
|
var UPG_BASE_NODE_FIELDS = Object.freeze(
|
|
@@ -7198,6 +7295,22 @@ var TEMPLATE_LIFECYCLES = [
|
|
|
7198
7295
|
fromTemplate("nps_campaign", OPERATIONAL_TEMPLATE),
|
|
7199
7296
|
fromTemplate("monitor", OPERATIONAL_TEMPLATE),
|
|
7200
7297
|
fromTemplate("alert_rule", OPERATIONAL_TEMPLATE),
|
|
7298
|
+
// 0.33.0: agent_skill and agent_hook move here from WORK_ITEM. A durable
|
|
7299
|
+
// capability and an event trigger were both reaching `in_review` and `done`,
|
|
7300
|
+
// which is the wrong shape rather than a missing one: neither is work that gets
|
|
7301
|
+
// reviewed and accepted, and zero of the ten field instances across the corpus
|
|
7302
|
+
// had ever left the initial phase. Their nearest siblings are the two lines
|
|
7303
|
+
// directly above, and `monitor` and `alert_rule` are the same kind of thing:
|
|
7304
|
+
// something that runs continuously, can be paused, and is eventually retired.
|
|
7305
|
+
//
|
|
7306
|
+
// NOT lifecycle-free. The `user_story` precedent for lifecycle-freedom is the
|
|
7307
|
+
// Statement/Implementation split, where the paired `task` carries the
|
|
7308
|
+
// lifecycle. There is no paired entity here, so lifecycle-freedom would leave
|
|
7309
|
+
// `agent_skill` unable to say it is disabled and would entrench
|
|
7310
|
+
// `AgentHookProperties.hook_status` (now @deprecated) as the permanent status
|
|
7311
|
+
// axis, which is the *_status shadow Pattern D collapsed fourteen times.
|
|
7312
|
+
fromTemplate("agent_skill", OPERATIONAL_TEMPLATE),
|
|
7313
|
+
fromTemplate("agent_hook", OPERATIONAL_TEMPLATE),
|
|
7201
7314
|
fromTemplate("on_call_rotation", OPERATIONAL_TEMPLATE),
|
|
7202
7315
|
fromTemplate("security_audit", OPERATIONAL_TEMPLATE),
|
|
7203
7316
|
fromTemplate("penetration_test", OPERATIONAL_TEMPLATE),
|
|
@@ -7226,8 +7339,8 @@ var TEMPLATE_LIFECYCLES = [
|
|
|
7226
7339
|
fromTemplate("infrastructure_component", MATURITY_TEMPLATE),
|
|
7227
7340
|
// ── Phase C: WORK_ITEM (todo / in_progress / in_review / done) ──────────────
|
|
7228
7341
|
fromTemplate("agent_task", WORK_ITEM_TEMPLATE),
|
|
7229
|
-
|
|
7230
|
-
|
|
7342
|
+
// agent_skill and agent_hook moved WORK_ITEM -> OPERATIONAL at 0.33.0; see the
|
|
7343
|
+
// OPERATIONAL block below. agent_task stays: a task IS a work item.
|
|
7231
7344
|
// UPG-690 (0.21.0): task (exact match) + epic (todo→in_progress→done ⊆ WORK_ITEM,
|
|
7232
7345
|
// in_review becomes available — non-breaking widening) folded off hand-authored.
|
|
7233
7346
|
fromTemplate("task", WORK_ITEM_TEMPLATE),
|
|
@@ -10292,6 +10405,16 @@ function getScalarToEdgeMigrations(fromVersion, toVersion) {
|
|
|
10292
10405
|
return result;
|
|
10293
10406
|
}
|
|
10294
10407
|
var UPG_EDGE_MIGRATIONS = {
|
|
10408
|
+
"0.33.0": [
|
|
10409
|
+
// G1 — the project membership edge widens from epic to the work-item set.
|
|
10410
|
+
// Same construction as the 0.32.0 cadence rename directly below, and for the
|
|
10411
|
+
// same measured reason: the adapters default an unrecognised issue to `task`,
|
|
10412
|
+
// so the type a project most needed to deliver was the one type it could not
|
|
10413
|
+
// reach. No requires_target_type, because the destination's target_type is
|
|
10414
|
+
// the `node` wildcard and endpoint guards check the catalog's declared type
|
|
10415
|
+
// rather than the concrete instance. Gate only on the unwidened source.
|
|
10416
|
+
{ kind: "rename", from: "project_delivers_epic", to: "project_delivers_work_item", requires_source_type: "project", reason: "0.33.0: a project could only deliver an epic, while a Linear dry-run carried 651 project memberships as properties.linear_project_id and emitted zero edges. Widened to the polymorphic work-item endpoint; no flip, source unchanged. Measured live population of the old type at release: one, in the CI saturation fixture, so this rename is a vocabulary change rather than a data migration." }
|
|
10417
|
+
],
|
|
10295
10418
|
"0.32.0": [
|
|
10296
10419
|
// C5 — the cadence scheduling edge widens from user_story to the work-item
|
|
10297
10420
|
// set. No requires_target_type: the destination's target_type is the `node`
|
|
@@ -11110,6 +11233,41 @@ function migrateEdge(edge, fromVersion, toVersion, endpoints) {
|
|
|
11110
11233
|
// src/grammar/status-migrations.ts
|
|
11111
11234
|
var UPG_STATUS_MIGRATIONS = {
|
|
11112
11235
|
// ── Engineering / Operations ─────────────────────────────────────────────
|
|
11236
|
+
// ── Agent automation: WORK_ITEM -> OPERATIONAL (0.33.0) ──────────────────
|
|
11237
|
+
// agent_skill and agent_hook carried WORK_ITEM until 0.33.0, so every graph
|
|
11238
|
+
// written before it holds WORK_ITEM phase ids against an OPERATIONAL
|
|
11239
|
+
// lifecycle. Measured population at release: 4 nodes, all `todo`. The other
|
|
11240
|
+
// five entries cover the phases those graphs could legally have reached.
|
|
11241
|
+
//
|
|
11242
|
+
// in_review maps to `active` rather than to a review phase because OPERATIONAL
|
|
11243
|
+
// has none: a hook awaiting review is still a hook that is running or about to.
|
|
11244
|
+
// cancelled maps to `sunset` rather than `completed`: OPERATIONAL has no
|
|
11245
|
+
// cancelled bucket and a discontinued trigger is wound down, not finished.
|
|
11246
|
+
agent_skill: {
|
|
11247
|
+
todo: "planning",
|
|
11248
|
+
backlog: "planning",
|
|
11249
|
+
in_progress: "active",
|
|
11250
|
+
in_review: "active",
|
|
11251
|
+
done: "completed",
|
|
11252
|
+
cancelled: "sunset"
|
|
11253
|
+
},
|
|
11254
|
+
agent_hook: {
|
|
11255
|
+
todo: "planning",
|
|
11256
|
+
backlog: "planning",
|
|
11257
|
+
in_progress: "active",
|
|
11258
|
+
in_review: "active",
|
|
11259
|
+
done: "completed",
|
|
11260
|
+
cancelled: "sunset",
|
|
11261
|
+
// The AgentHookProperties.hook_status vocabulary, @deprecated at 0.33.0 as a
|
|
11262
|
+
// *_status shadow of the base `status`. `error` maps to `paused` and NOT to a
|
|
11263
|
+
// failure phase: OPERATIONAL has none, and runtime health is store or
|
|
11264
|
+
// telemetry state rather than a fact about the thing. That is the same cut
|
|
11265
|
+
// that kept composition.rev, which is a fact, and excluded a concurrency
|
|
11266
|
+
// token, which is not. The failure detail belongs in `description` or a
|
|
11267
|
+
// diagnostic property.
|
|
11268
|
+
disabled: "paused",
|
|
11269
|
+
error: "paused"
|
|
11270
|
+
},
|
|
11113
11271
|
service: {
|
|
11114
11272
|
// Lifecycle: [development, staging, production, deprecated]
|
|
11115
11273
|
// Authoring habit from before lifecycle adoption: every long-lived
|
|
@@ -11559,7 +11717,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11559
11717
|
agent_hook: {
|
|
11560
11718
|
hook_event: { type: "string", description: "Event that triggers this hook" },
|
|
11561
11719
|
hook_action: { type: "string", description: "Action performed when the hook fires" },
|
|
11562
|
-
hook_status: { type: "string", enum: ["active", "disabled", "error"], description: "Operational status of the hook" },
|
|
11720
|
+
hook_status: { type: "string", enum: ["active", "disabled", "error"], description: "Operational status of the hook. @deprecated since 0.33.0, removeIn 1.0.0. Use the base `status` field. `agent_hook` moved from the WORK_ITEM lifecycle to OPERATIONAL at 0.33.0, whose phases carry this axis directly: `active` maps to `active` and `disabled` maps to `paused`. A `*_status` property beside the base `status` is the shadow that Pattern D collapsed fourteen times at 0.15.0, and a type that models its own status in a property is a type whose lifecycle did not fit. `error` maps to `paused`: OPERATIONAL has no failure phase, and runtime health is store or telemetry state rather than a fact about the thing, which is the same cut that keeps `composition.rev` and excludes a concurrency token. Put the failure detail in `description` or a diagnostic property." },
|
|
11563
11721
|
execution_count: { type: "number", description: "Number of times this hook has fired", modifier: "snapshot" }
|
|
11564
11722
|
},
|
|
11565
11723
|
// AgentSessionProperties: Agent session.
|
|
@@ -12096,12 +12254,12 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12096
12254
|
},
|
|
12097
12255
|
// CompositionProperties
|
|
12098
12256
|
composition: {
|
|
12257
|
+
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
12258
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
12099
12259
|
members: { type: "object[]", description: "The frozen member arrangement, captured at publish. Layout and pointers only, never resolved content." },
|
|
12100
12260
|
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." },
|
|
12101
12261
|
published_at: { type: "string", description: "ISO timestamp of the most recent publish or republish." },
|
|
12102
|
-
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
12103
|
-
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." },
|
|
12104
|
-
presentation: { type: "object", description: "Advisory rendering intent for the composition as a whole. A consumer may ignore it entirely and still be conformant." }
|
|
12262
|
+
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
12105
12263
|
},
|
|
12106
12264
|
// ConfigurationAxisProperties: A named dimension along which the product's composition differs.
|
|
12107
12265
|
configuration_axis: {
|
|
@@ -12364,7 +12522,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12364
12522
|
budget: { type: "number", description: "Allocated budget for this program" },
|
|
12365
12523
|
target_leads: { type: "number", description: "Target number of leads to generate" }
|
|
12366
12524
|
},
|
|
12367
|
-
// DepartmentProperties: Department entity.
|
|
12525
|
+
// DepartmentProperties: Department entity. A department is the single org tier above a team.
|
|
12368
12526
|
department: {
|
|
12369
12527
|
headcount: { type: "number", description: "Total number of people in the department", modifier: "snapshot" },
|
|
12370
12528
|
budget: { type: "number", description: "Annual budget allocated to the department" },
|
|
@@ -13518,7 +13676,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13518
13676
|
},
|
|
13519
13677
|
// ObjectiveProperties: A high-level strategic goal. The O in OKR.
|
|
13520
13678
|
objective: {
|
|
13521
|
-
timeframe: { type: "string", description: 'Planning timeframe (e.g. "Q1 2026", "H1 2026")' },
|
|
13679
|
+
timeframe: { type: "string", description: 'Planning timeframe (e.g. "Q1 2026", "H1 2026"). @deprecated since 0.33.0, removeIn 1.0.0. Promote the period to a `planning_cycle` node and link it with the `objective_scoped_to_planning_cycle` edge, which has existed since 0.20.0 and points at a shared, dated, nestable interval instead of a drifting per-objective string. Until promoted, the value is a display label and nothing schedules on it. The promotion is documented rather than automated and no `drop_props` migration ships: which cycle a free-text string means, and whether one exists yet, is a judgement, and dropping the string before the cycle exists destroys the only record of the intent. @example "Q1 2026", "H1 2026"' },
|
|
13522
13680
|
progress: { type: "number", description: "Overall progress (0\u2013100)" }
|
|
13523
13681
|
},
|
|
13524
13682
|
// ObservationProperties: Discrete observation captured during research. Absorbed from the deprecated Highlight entity.
|
|
@@ -13739,7 +13897,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13739
13897
|
logo_url: { type: "string", description: "Logo or icon URL. Used to render product cards and lists.", modifier: "volatile" },
|
|
13740
13898
|
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" },
|
|
13741
13899
|
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." },
|
|
13742
|
-
key_prefix: { type: "string", description: 'Prefix for keys minted onto this product\'s nodes (e.g. `"LTN"`, giving `LTN-1`, `LTN-2`,
|
|
13900
|
+
key_prefix: { type: "string", description: 'Prefix for keys minted onto this product\'s nodes (e.g. `"LTN"`, giving `LTN-1`, `LTN-2`, ...). Retained as the single-team unasked default. @deprecated since 0.33.0, removeIn 1.0.0. Use `team.key_prefix`. This field\'s own summary called the prefix "the namespace a tracker calls a team", and a single string cannot express a product with two of them: a two-team product has never been expressible, which is a defect rather than an unused feature. Ignored whenever any team in the product declares a prefix. Deprecated rather than removed because removal would strand every graph that has a product prefix and no `team` nodes. @example "LTN"', notes: "Pairs with `UPGBaseNode.key`, which holds the minted value and states the resolution order normatively. 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. WHY THIS IS NOT A DEFAULT WITH `team.key_prefix` AS AN OVERRIDE. A default and an override describe one fact at two scopes. These describe different facts, and this one is correct only when the product has exactly one team, which is an accident of arity rather than a scope. Keeping it live as a fallback would also reintroduce the defect: a prefix that still resolves wins on ladder order, so a legacy-prefixed product with two teams would offer no choice at all and mint everything under the legacy prefix silently, with migration order deciding the behaviour per graph." }
|
|
13743
13901
|
},
|
|
13744
13902
|
// ProductAreaProperties: ProductArea entity.
|
|
13745
13903
|
product_area: {
|
|
@@ -14228,7 +14386,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14228
14386
|
// StrategicThemeProperties: StrategicTheme entity.
|
|
14229
14387
|
strategic_theme: {
|
|
14230
14388
|
owner: { type: "string", description: "Owning person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
|
|
14231
|
-
time_horizon: { type: "string", description: 'Bounded period this theme is active (a theme is time-bound, within a pillar). @deprecated since 0.20.0. Promote the period to a `planning_cycle` node and link it with the `strategic_theme_scoped_to_planning_cycle` edge, which points at a shared, dated, nestable interval instead of a drifting per-theme string. Kept (not removed) for back-compat
|
|
14389
|
+
time_horizon: { type: "string", description: 'Bounded period this theme is active (a theme is time-bound, within a pillar). @deprecated since 0.20.0, removeIn 1.0.0. Promote the period to a `planning_cycle` node and link it with the `strategic_theme_scoped_to_planning_cycle` edge, which points at a shared, dated, nestable interval instead of a drifting per-theme string. Kept (not removed) for back-compat. The removal version is stated at 0.33.0 because an undated deprecation is how this field survived twelve minors; the promotion is documented rather than automated and no `drop_props` migration ships. `strategic_pillar.time_horizon` stays as-is (a durable pillar horizon is genuinely open-ended, not a dated cycle). @example "Q1 2026", "FY26"' },
|
|
14232
14390
|
description: { type: "string", description: "Short narrative of this time-bound thrust within its pillar" },
|
|
14233
14391
|
scope: { type: "string", description: "What the theme explicitly includes or excludes" }
|
|
14234
14392
|
},
|
|
@@ -14347,7 +14505,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14347
14505
|
team: {
|
|
14348
14506
|
team_type: { type: "string", enum: ["product", "engineering", "design", "growth", "customer_success"], description: "Functional area of the team" },
|
|
14349
14507
|
size: { type: "number", description: "Number of people on the team" },
|
|
14350
|
-
mission: { type: "string", description: "Team's mission statement" }
|
|
14508
|
+
mission: { type: "string", description: "Team's mission statement" },
|
|
14509
|
+
key_prefix: { type: "string", description: 'Prefix this team mints node keys with (e.g. `"ENTP"`, giving `ENTP-1`, `ENTP-2`, ...). @example "ENTP"', notes: "SUPERSEDES `product.key_prefix` FOR THIS PRODUCT. The moment any team declares a prefix, the product-level prefix stops being consulted and the declared team prefixes become the candidate set a create surface offers. A product-level prefix that still resolved would win on order alone, and a multi-team product would then silently mint everything under it, which is the defect this field exists to end. A DECLARED PREFIX IS A CANDIDATE BEFORE IT IS OBSERVED. Candidates derived only from keys that already exist cannot see a team's first create, which is the one that most needs asking about. UNIQUENESS IS PRODUCT-SCOPED, AND A TEAM PREFIX DOES NOT WIDEN IT. The key sequence runs per product across entity types, so two teams in one product share one number line and never collide with each other. A team prefix names a team WITHIN that scope; it does not create a sequence that spans products. MINTING IS PRODUCT-SCOPED (normative, 0.33.0). `team` is `portfolio_shared`, so one team node can be referenced from two products. Minting is not portfolio-scoped with it. The rule, and it is a requirement rather than a recommendation: 1. A portfolio-shared team's prefix is NOT a minting candidate in a second product. The first product a team mints under is the only product that prefix mints in. 2. Refusal is NO-KEY. The node is created without a key. Refusal MUST NOT be an exception: a keyless create is a legal outcome everywhere else in the ladder, and throwing here would break creates that succeed today on every surface. Throwing stays reserved for a broken invariant, not for policy. 3. The rule applies on the MINT path, not the picker path, and it covers the INFERRED case. A prefix that was never requested by anyone, and was derived from keys that already exist in the second product, is refused on the same terms as one a caller named. A guard that only inspects an explicitly requested prefix misses the quiet path, and the quiet path is the one a fixture reproduces. WHY THE SCOPE STOPS AT THE PRODUCT. Key uniqueness is enforced by a `(product_id, key)` index, which permits the same key under two product ids by construction. A prefix that minted in two products would therefore run two independent sequences under one name and hand two different nodes the same citation, with nothing objecting. Portfolio-shared team minting is DEFERRED until a supra-product uniqueness design exists, rather than approximated. This rule states the CONTRACT. The behaviour belongs to whatever mints keys, which is not this package: see `UPGBaseNode.key`." }
|
|
14351
14510
|
},
|
|
14352
14511
|
// TeamOkrProperties: TeamOkr entity.
|
|
14353
14512
|
team_okr: {
|
|
@@ -14673,6 +14832,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14673
14832
|
},
|
|
14674
14833
|
// WorkspaceProperties: Workspace: a spatial thinking space for arranging entities.
|
|
14675
14834
|
workspace: {
|
|
14835
|
+
member_query: { type: "object", description: "When present, membership is DERIVED: members are produced by running this query rather than authored by placement.", notes: "This is the portable statement of what the layer shows. On a composition, `CompositionMember.href` remains the publishing tool's own resolved route and stays opaque to everyone else; a member may carry both, and then the href is a fast path while the query is the meaning. A consumer that cannot parse the href can still render the layer, which is the whole reason the declaration is here rather than in a tool-namespaced bag key. A layer with no `member_query` is authored, which is what every composition written before 0.32.0 is. DECLARED ON BOTH HALVES OF THE PAIR since 0.33.0. A layer is query-driven while it is being worked on, not only once it is published, so declaring the query only on the durable composition would make it a fact invented at publish time rather than one recorded." },
|
|
14836
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
14676
14837
|
visibility: { type: "string", enum: ["private", "shared", "public"], description: "Who can see this workspace" },
|
|
14677
14838
|
purpose: { type: "string", description: "Free-text description. Pairs with the closed-enum `workspace_purpose`." },
|
|
14678
14839
|
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." },
|
|
@@ -32878,6 +33039,9 @@ var NODE_KEY_ORDER = [
|
|
|
32878
33039
|
"external_tool",
|
|
32879
33040
|
"external_ref",
|
|
32880
33041
|
"external_id",
|
|
33042
|
+
"external_links",
|
|
33043
|
+
"created_at",
|
|
33044
|
+
"updated_at",
|
|
32881
33045
|
"sort_order",
|
|
32882
33046
|
"properties"
|
|
32883
33047
|
];
|
|
@@ -33160,7 +33324,7 @@ function checkHeaderSealText(text) {
|
|
|
33160
33324
|
}
|
|
33161
33325
|
|
|
33162
33326
|
// src/index.ts
|
|
33163
|
-
var UPG_VERSION = "0.
|
|
33327
|
+
var UPG_VERSION = "0.33.1";
|
|
33164
33328
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
33165
33329
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
33166
33330
|
var UPG_TYPES = getTypes();
|
|
@@ -33246,6 +33410,7 @@ var UPG_META_COUNT = UPG_ENTITY_META.length;
|
|
|
33246
33410
|
LEGACY_PRODUCT_STAGES,
|
|
33247
33411
|
MARKDOWN_FORMAT_VERSION,
|
|
33248
33412
|
MARKET_COMPETITIVE_PLAYBOOK,
|
|
33413
|
+
NODE_KEY_ORDER,
|
|
33249
33414
|
OPERATIONS_QUALITY_PLAYBOOK,
|
|
33250
33415
|
PRESENT_UNDER_PROPERTY,
|
|
33251
33416
|
PRODUCT_DELIVERY_PLAYBOOK,
|