@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/dist/index.js
CHANGED
|
@@ -2439,6 +2439,53 @@ var UPG_EDGE_CATALOG = {
|
|
|
2439
2439
|
// contained by product / department / team. Hierarchy edges for person
|
|
2440
2440
|
// are deliberately omitted; adding them only to satisfy a hierarchy
|
|
2441
2441
|
// audit would invert the orthogonality the type was introduced to express.
|
|
2442
|
+
// ── Org membership and reporting (0.33.0) ───────────────────────────────────
|
|
2443
|
+
// Both edges are `semantic` REFERENCE edges and neither is a reversal of
|
|
2444
|
+
// the containment-free stance recorded immediately above. That note forecloses
|
|
2445
|
+
// HIERARCHY edges added ONLY to satisfy a hierarchy audit; these are neither.
|
|
2446
|
+
// UPG_CONTAINMENT_FREE_TYPES, UPG_VALID_CHILDREN, get_tree and G2b are all
|
|
2447
|
+
// untouched, and node_assigned_to_person was minted cross-domain onto `person`
|
|
2448
|
+
// at 0.32.0 on the same reading.
|
|
2449
|
+
//
|
|
2450
|
+
// DIRECTION IS LOAD-BEARING, not a preference. get_tree reads SOURCE as parent
|
|
2451
|
+
// (see the team_contains_team note above), so a team -> person edge would sit
|
|
2452
|
+
// one classification change away from putting people in the org tree. Sourcing
|
|
2453
|
+
// on `person` makes the containment reading unavailable rather than merely
|
|
2454
|
+
// unchosen. `team_includes_person` classified hierarchy is the shape to refuse.
|
|
2455
|
+
//
|
|
2456
|
+
// CLASSIFICATION IS `semantic` AND NOT `cross-domain`, which the 0.33.0 design
|
|
2457
|
+
// recommended before the guardrail was consulted. `person` and `team` both live
|
|
2458
|
+
// in the team_org domain, so a `cross-domain` label would contradict itself: the
|
|
2459
|
+
// edge crosses no domain boundary, and T1.7 forbids NEW same-domain cross-domain
|
|
2460
|
+
// edges precisely so that debt stops growing. Nothing the design argument
|
|
2461
|
+
// depends on moves: `semantic` enters neither UPG_VALID_CHILDREN nor get_tree,
|
|
2462
|
+
// and G2b is not engaged.
|
|
2463
|
+
// Membership is UNQUALIFIED: no role property on the edge. `role` is already an
|
|
2464
|
+
// entity type with team_staffed_with_role, so a role string here would be a
|
|
2465
|
+
// second role model beside a node type. The consequence is stated rather than
|
|
2466
|
+
// hidden: the graph can say a team has a slot and that a person is on the team,
|
|
2467
|
+
// and not that the person fills the slot. `person_holds_role` is banked, with
|
|
2468
|
+
// its condition the first field graph carrying both where the join is asked for.
|
|
2469
|
+
//
|
|
2470
|
+
// Neither is cross_product_eligible: `person` is not portfolio_shared, so the
|
|
2471
|
+
// 0.18.0 both-endpoints gate rejects the flag regardless of any team-scoping
|
|
2472
|
+
// ruling.
|
|
2473
|
+
person_member_of_team: { forward_verb: "member_of", reverse_verb: "has_member", classification: "semantic", source_type: "person", target_type: "team" },
|
|
2474
|
+
// Cited three times in spec source since 0.17.2 and existing zero times: the
|
|
2475
|
+
// team_contains_team note above, the team-nesting domain guide, and an
|
|
2476
|
+
// anti_patterns entry that instructs users to model a cross-department
|
|
2477
|
+
// reporting line with this edge. An anti-pattern guide is instructional by
|
|
2478
|
+
// construction, so that third site was telling users to use an edge the catalog
|
|
2479
|
+
// would refuse. Minting makes all three true and needs no prose edit.
|
|
2480
|
+
//
|
|
2481
|
+
// semantic, not hierarchy, and the temptation is real: a reporting line IS a
|
|
2482
|
+
// hierarchy in ordinary English, which is exactly why classifying it that way
|
|
2483
|
+
// would put `person` into containment and become the reversal the sibling edge
|
|
2484
|
+
// above is shaped to avoid. The org chart is a reference structure over
|
|
2485
|
+
// containment-free people. Same classification as the membership edge above, so
|
|
2486
|
+
// the person / team / department triangle is decided once rather than three
|
|
2487
|
+
// times.
|
|
2488
|
+
person_reports_to_person: { forward_verb: "reports_to", reverse_verb: "has_report", classification: "semantic", source_type: "person", target_type: "person" },
|
|
2442
2489
|
team_staffed_with_role: { forward_verb: "staffed_with", reverse_verb: "staffed_in", classification: "hierarchy", source_type: "team", target_type: "role" },
|
|
2443
2490
|
team_targets_team_okr: { forward_verb: "targets", reverse_verb: "targeted_by", classification: "hierarchy", source_type: "team", target_type: "team_okr" },
|
|
2444
2491
|
team_reflects_in_retrospective: { forward_verb: "reflects_in", reverse_verb: "reflects", classification: "hierarchy", source_type: "team", target_type: "retrospective" },
|
|
@@ -2483,7 +2530,31 @@ var UPG_EDGE_CATALOG = {
|
|
|
2483
2530
|
program_reported_via_status_report: { forward_verb: "reported_via", reverse_verb: "reports", classification: "hierarchy", source_type: "program", target_type: "status_report" },
|
|
2484
2531
|
program_contains_epic: { forward_verb: "contains", reverse_verb: "contained_in", classification: "cross-domain", source_type: "program", target_type: "epic" },
|
|
2485
2532
|
project_implements_initiative: { forward_verb: "implements", reverse_verb: "implemented_by", classification: "cross-domain", source_type: "project", target_type: "initiative" },
|
|
2486
|
-
|
|
2533
|
+
// WIDENED AT 0.33.0, renamed from project_delivers_epic. The epic-only
|
|
2534
|
+
// endpoint could not hold a real tracker import: a Linear dry-run carried 651
|
|
2535
|
+
// project memberships as `properties.linear_project_id` and emitted zero
|
|
2536
|
+
// edges, because the default issue type an adapter produces is `task` and the
|
|
2537
|
+
// only project edge could not reach it. Endpoint-polymorphic over the
|
|
2538
|
+
// work-item set {feature, epic, user_story, task, bug} by the same
|
|
2539
|
+
// construction as planning_cycle_schedules_work_item: the `work_item` token
|
|
2540
|
+
// names the intended semantic domain, the endpoint is the `node` wildcard.
|
|
2541
|
+
//
|
|
2542
|
+
// NOT a `contains` verb, and UPG_VALID_CHILDREN.project is deliberately
|
|
2543
|
+
// untouched. `contains` is this catalog's containment verb and every edge
|
|
2544
|
+
// using it is classification: 'hierarchy', which under G2b obliges a matching
|
|
2545
|
+
// UPG_VALID_CHILDREN pair. That map is Record<parent, child[]> of concrete
|
|
2546
|
+
// type names, so a polymorphic endpoint can never discharge the obligation
|
|
2547
|
+
// its own verb would create. A work item is CONTAINED once (by its epic or
|
|
2548
|
+
// feature) and REFERENCED many times; the parent axis is containment and the
|
|
2549
|
+
// project axis is a reference. Giving project a second containment claim over
|
|
2550
|
+
// the same task is the two-trees problem.
|
|
2551
|
+
//
|
|
2552
|
+
// classification moves cross-domain -> semantic with the widening: pinned to
|
|
2553
|
+
// project -> epic it was a cross-domain pair, and once the endpoint is the
|
|
2554
|
+
// wildcard it is a reference relation between a planning container and
|
|
2555
|
+
// arbitrary work. deliberate_only because project membership is authored,
|
|
2556
|
+
// never inferred from co-occurrence.
|
|
2557
|
+
project_delivers_work_item: { forward_verb: "delivers", reverse_verb: "delivered_by", classification: "semantic", source_type: "project", target_type: "node", deliberate_only: true },
|
|
2487
2558
|
milestone_gates_release: { forward_verb: "gates", reverse_verb: "gated_by", classification: "cross-domain", source_type: "milestone", target_type: "release" },
|
|
2488
2559
|
milestone_triggers_release: { forward_verb: "triggers", reverse_verb: "triggered_by", classification: "cross-domain", source_type: "milestone", target_type: "release" },
|
|
2489
2560
|
deliverable_ships_feature: { forward_verb: "ships", reverse_verb: "shipped_by", classification: "cross-domain", source_type: "deliverable", target_type: "feature" },
|
|
@@ -3549,6 +3620,24 @@ var UPG_EDGE_CATALOG = {
|
|
|
3549
3620
|
// so the lifecycle is governed rather than a standalone root (0.12.1 refinement).
|
|
3550
3621
|
operating_lifecycle_defined_by_specification: { forward_verb: "defined_by", reverse_verb: "defines", classification: "semantic", source_type: "operating_lifecycle", target_type: "specification" },
|
|
3551
3622
|
operating_lifecycle_contains_operating_stage: { forward_verb: "contains", reverse_verb: "belongs_to", classification: "hierarchy", source_type: "operating_lifecycle", target_type: "operating_stage" },
|
|
3623
|
+
// Conformance (0.33.0, RE-OPENED). 0.33.0 planned to mint
|
|
3624
|
+
// product_conforms_to_specification and feature_conforms_to_specification here.
|
|
3625
|
+
// It does not, and the reason is a fact the design missed: the product-to-
|
|
3626
|
+
// foundation links are NOT absent, they live one tier up. See
|
|
3627
|
+
// UPG_CROSS_ONLY_EDGE_TYPES in shapes/document.ts, which has carried
|
|
3628
|
+
// `product_implements_specification`, `product_exposes_specification` and
|
|
3629
|
+
// `feature_conforms_to_specification` as portfolio-native cross-product edges
|
|
3630
|
+
// since 0.9.12, exactly as the section comment above says. The tiers are
|
|
3631
|
+
// disjoint by construction, so minting the same key here is a hard collision,
|
|
3632
|
+
// and minting only the product half would split one question across two tiers
|
|
3633
|
+
// under two near-synonymous names.
|
|
3634
|
+
//
|
|
3635
|
+
// What survives is the real gap, stated here rather than closed: a
|
|
3636
|
+
// `specification` node held INSIDE a single product graph, with no portfolio,
|
|
3637
|
+
// has no conformance edge, because the cross-only tier needs a registry target.
|
|
3638
|
+
// Whether that case is closed by widening the tier or by a catalog edge under a
|
|
3639
|
+
// non-colliding key is a design question and is banked, with its condition the
|
|
3640
|
+
// first single-product graph that models a specification it conforms to.
|
|
3552
3641
|
// Stewardship: a registry specification is governed by a (registry-hostable) organization (0.9.13).
|
|
3553
3642
|
specification_governed_by_organization: { forward_verb: "governed_by", reverse_verb: "governs", classification: "semantic", source_type: "specification", target_type: "organization" },
|
|
3554
3643
|
// Operating-lifecycle cross-edges (0.11.6): a product's journey phase realises a canonical
|
|
@@ -3763,7 +3852,11 @@ var UPG_POLYMORPHIC_EDGE_KEYS = [
|
|
|
3763
3852
|
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
3764
3853
|
// bug}. The `work_item` key token names the intended semantic domain; the
|
|
3765
3854
|
// endpoint is the `node` wildcard.
|
|
3766
|
-
"planning_cycle_schedules_work_item"
|
|
3855
|
+
"planning_cycle_schedules_work_item",
|
|
3856
|
+
// Project membership (0.33.0, widened from project_delivers_epic):
|
|
3857
|
+
// endpoint-polymorphic over the work-item set {feature, epic, user_story, task,
|
|
3858
|
+
// bug}. Same construction and same reasoning as the cadence edge above.
|
|
3859
|
+
"project_delivers_work_item"
|
|
3767
3860
|
];
|
|
3768
3861
|
var _POLY_KEY_SET = new Set(UPG_POLYMORPHIC_EDGE_KEYS);
|
|
3769
3862
|
function isPolymorphicEdge(key) {
|
|
@@ -3820,6 +3913,9 @@ var BASE_NODE_FIELD_PRESENCE = {
|
|
|
3820
3913
|
external_tool: true,
|
|
3821
3914
|
external_ref: true,
|
|
3822
3915
|
external_id: true,
|
|
3916
|
+
external_links: true,
|
|
3917
|
+
created_at: true,
|
|
3918
|
+
updated_at: true,
|
|
3823
3919
|
properties: true
|
|
3824
3920
|
};
|
|
3825
3921
|
var UPG_BASE_NODE_FIELDS = Object.freeze(
|
|
@@ -6911,6 +7007,22 @@ var TEMPLATE_LIFECYCLES = [
|
|
|
6911
7007
|
fromTemplate("nps_campaign", OPERATIONAL_TEMPLATE),
|
|
6912
7008
|
fromTemplate("monitor", OPERATIONAL_TEMPLATE),
|
|
6913
7009
|
fromTemplate("alert_rule", OPERATIONAL_TEMPLATE),
|
|
7010
|
+
// 0.33.0: agent_skill and agent_hook move here from WORK_ITEM. A durable
|
|
7011
|
+
// capability and an event trigger were both reaching `in_review` and `done`,
|
|
7012
|
+
// which is the wrong shape rather than a missing one: neither is work that gets
|
|
7013
|
+
// reviewed and accepted, and zero of the ten field instances across the corpus
|
|
7014
|
+
// had ever left the initial phase. Their nearest siblings are the two lines
|
|
7015
|
+
// directly above, and `monitor` and `alert_rule` are the same kind of thing:
|
|
7016
|
+
// something that runs continuously, can be paused, and is eventually retired.
|
|
7017
|
+
//
|
|
7018
|
+
// NOT lifecycle-free. The `user_story` precedent for lifecycle-freedom is the
|
|
7019
|
+
// Statement/Implementation split, where the paired `task` carries the
|
|
7020
|
+
// lifecycle. There is no paired entity here, so lifecycle-freedom would leave
|
|
7021
|
+
// `agent_skill` unable to say it is disabled and would entrench
|
|
7022
|
+
// `AgentHookProperties.hook_status` (now @deprecated) as the permanent status
|
|
7023
|
+
// axis, which is the *_status shadow Pattern D collapsed fourteen times.
|
|
7024
|
+
fromTemplate("agent_skill", OPERATIONAL_TEMPLATE),
|
|
7025
|
+
fromTemplate("agent_hook", OPERATIONAL_TEMPLATE),
|
|
6914
7026
|
fromTemplate("on_call_rotation", OPERATIONAL_TEMPLATE),
|
|
6915
7027
|
fromTemplate("security_audit", OPERATIONAL_TEMPLATE),
|
|
6916
7028
|
fromTemplate("penetration_test", OPERATIONAL_TEMPLATE),
|
|
@@ -6939,8 +7051,8 @@ var TEMPLATE_LIFECYCLES = [
|
|
|
6939
7051
|
fromTemplate("infrastructure_component", MATURITY_TEMPLATE),
|
|
6940
7052
|
// ── Phase C: WORK_ITEM (todo / in_progress / in_review / done) ──────────────
|
|
6941
7053
|
fromTemplate("agent_task", WORK_ITEM_TEMPLATE),
|
|
6942
|
-
|
|
6943
|
-
|
|
7054
|
+
// agent_skill and agent_hook moved WORK_ITEM -> OPERATIONAL at 0.33.0; see the
|
|
7055
|
+
// OPERATIONAL block below. agent_task stays: a task IS a work item.
|
|
6944
7056
|
// UPG-690 (0.21.0): task (exact match) + epic (todo→in_progress→done ⊆ WORK_ITEM,
|
|
6945
7057
|
// in_review becomes available — non-breaking widening) folded off hand-authored.
|
|
6946
7058
|
fromTemplate("task", WORK_ITEM_TEMPLATE),
|
|
@@ -10005,6 +10117,16 @@ function getScalarToEdgeMigrations(fromVersion, toVersion) {
|
|
|
10005
10117
|
return result;
|
|
10006
10118
|
}
|
|
10007
10119
|
var UPG_EDGE_MIGRATIONS = {
|
|
10120
|
+
"0.33.0": [
|
|
10121
|
+
// G1 — the project membership edge widens from epic to the work-item set.
|
|
10122
|
+
// Same construction as the 0.32.0 cadence rename directly below, and for the
|
|
10123
|
+
// same measured reason: the adapters default an unrecognised issue to `task`,
|
|
10124
|
+
// so the type a project most needed to deliver was the one type it could not
|
|
10125
|
+
// reach. No requires_target_type, because the destination's target_type is
|
|
10126
|
+
// the `node` wildcard and endpoint guards check the catalog's declared type
|
|
10127
|
+
// rather than the concrete instance. Gate only on the unwidened source.
|
|
10128
|
+
{ 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." }
|
|
10129
|
+
],
|
|
10008
10130
|
"0.32.0": [
|
|
10009
10131
|
// C5 — the cadence scheduling edge widens from user_story to the work-item
|
|
10010
10132
|
// set. No requires_target_type: the destination's target_type is the `node`
|
|
@@ -10823,6 +10945,41 @@ function migrateEdge(edge, fromVersion, toVersion, endpoints) {
|
|
|
10823
10945
|
// src/grammar/status-migrations.ts
|
|
10824
10946
|
var UPG_STATUS_MIGRATIONS = {
|
|
10825
10947
|
// ── Engineering / Operations ─────────────────────────────────────────────
|
|
10948
|
+
// ── Agent automation: WORK_ITEM -> OPERATIONAL (0.33.0) ──────────────────
|
|
10949
|
+
// agent_skill and agent_hook carried WORK_ITEM until 0.33.0, so every graph
|
|
10950
|
+
// written before it holds WORK_ITEM phase ids against an OPERATIONAL
|
|
10951
|
+
// lifecycle. Measured population at release: 4 nodes, all `todo`. The other
|
|
10952
|
+
// five entries cover the phases those graphs could legally have reached.
|
|
10953
|
+
//
|
|
10954
|
+
// in_review maps to `active` rather than to a review phase because OPERATIONAL
|
|
10955
|
+
// has none: a hook awaiting review is still a hook that is running or about to.
|
|
10956
|
+
// cancelled maps to `sunset` rather than `completed`: OPERATIONAL has no
|
|
10957
|
+
// cancelled bucket and a discontinued trigger is wound down, not finished.
|
|
10958
|
+
agent_skill: {
|
|
10959
|
+
todo: "planning",
|
|
10960
|
+
backlog: "planning",
|
|
10961
|
+
in_progress: "active",
|
|
10962
|
+
in_review: "active",
|
|
10963
|
+
done: "completed",
|
|
10964
|
+
cancelled: "sunset"
|
|
10965
|
+
},
|
|
10966
|
+
agent_hook: {
|
|
10967
|
+
todo: "planning",
|
|
10968
|
+
backlog: "planning",
|
|
10969
|
+
in_progress: "active",
|
|
10970
|
+
in_review: "active",
|
|
10971
|
+
done: "completed",
|
|
10972
|
+
cancelled: "sunset",
|
|
10973
|
+
// The AgentHookProperties.hook_status vocabulary, @deprecated at 0.33.0 as a
|
|
10974
|
+
// *_status shadow of the base `status`. `error` maps to `paused` and NOT to a
|
|
10975
|
+
// failure phase: OPERATIONAL has none, and runtime health is store or
|
|
10976
|
+
// telemetry state rather than a fact about the thing. That is the same cut
|
|
10977
|
+
// that kept composition.rev, which is a fact, and excluded a concurrency
|
|
10978
|
+
// token, which is not. The failure detail belongs in `description` or a
|
|
10979
|
+
// diagnostic property.
|
|
10980
|
+
disabled: "paused",
|
|
10981
|
+
error: "paused"
|
|
10982
|
+
},
|
|
10826
10983
|
service: {
|
|
10827
10984
|
// Lifecycle: [development, staging, production, deprecated]
|
|
10828
10985
|
// Authoring habit from before lifecycle adoption: every long-lived
|
|
@@ -11272,7 +11429,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11272
11429
|
agent_hook: {
|
|
11273
11430
|
hook_event: { type: "string", description: "Event that triggers this hook" },
|
|
11274
11431
|
hook_action: { type: "string", description: "Action performed when the hook fires" },
|
|
11275
|
-
hook_status: { type: "string", enum: ["active", "disabled", "error"], description: "Operational status of the hook" },
|
|
11432
|
+
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." },
|
|
11276
11433
|
execution_count: { type: "number", description: "Number of times this hook has fired", modifier: "snapshot" }
|
|
11277
11434
|
},
|
|
11278
11435
|
// AgentSessionProperties: Agent session.
|
|
@@ -11809,12 +11966,12 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
11809
11966
|
},
|
|
11810
11967
|
// CompositionProperties
|
|
11811
11968
|
composition: {
|
|
11969
|
+
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." },
|
|
11970
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
11812
11971
|
members: { type: "object[]", description: "The frozen member arrangement, captured at publish. Layout and pointers only, never resolved content." },
|
|
11813
11972
|
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." },
|
|
11814
11973
|
published_at: { type: "string", description: "ISO timestamp of the most recent publish or republish." },
|
|
11815
|
-
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
11816
|
-
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." },
|
|
11817
|
-
presentation: { type: "object", description: "Advisory rendering intent for the composition as a whole. A consumer may ignore it entirely and still be conformant." }
|
|
11974
|
+
published_by: { type: "string", description: "Publisher handle or email. Display scalar, same posture as `WorkspaceProperties.owner`." }
|
|
11818
11975
|
},
|
|
11819
11976
|
// ConfigurationAxisProperties: A named dimension along which the product's composition differs.
|
|
11820
11977
|
configuration_axis: {
|
|
@@ -12077,7 +12234,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
12077
12234
|
budget: { type: "number", description: "Allocated budget for this program" },
|
|
12078
12235
|
target_leads: { type: "number", description: "Target number of leads to generate" }
|
|
12079
12236
|
},
|
|
12080
|
-
// DepartmentProperties: Department entity.
|
|
12237
|
+
// DepartmentProperties: Department entity. A department is the single org tier above a team.
|
|
12081
12238
|
department: {
|
|
12082
12239
|
headcount: { type: "number", description: "Total number of people in the department", modifier: "snapshot" },
|
|
12083
12240
|
budget: { type: "number", description: "Annual budget allocated to the department" },
|
|
@@ -13231,7 +13388,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13231
13388
|
},
|
|
13232
13389
|
// ObjectiveProperties: A high-level strategic goal. The O in OKR.
|
|
13233
13390
|
objective: {
|
|
13234
|
-
timeframe: { type: "string", description: 'Planning timeframe (e.g. "Q1 2026", "H1 2026")' },
|
|
13391
|
+
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"' },
|
|
13235
13392
|
progress: { type: "number", description: "Overall progress (0\u2013100)" }
|
|
13236
13393
|
},
|
|
13237
13394
|
// ObservationProperties: Discrete observation captured during research. Absorbed from the deprecated Highlight entity.
|
|
@@ -13452,7 +13609,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13452
13609
|
logo_url: { type: "string", description: "Logo or icon URL. Used to render product cards and lists.", modifier: "volatile" },
|
|
13453
13610
|
launched_at: { type: "string", description: "When the product became generally available (ISO 8601)" },
|
|
13454
13611
|
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." },
|
|
13455
|
-
key_prefix: { type: "string", description: 'Prefix for keys minted onto this product\'s nodes (e.g. `"LTN"`, giving `LTN-1`, `LTN-2`,
|
|
13612
|
+
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." }
|
|
13456
13613
|
},
|
|
13457
13614
|
// ProductAreaProperties: ProductArea entity.
|
|
13458
13615
|
product_area: {
|
|
@@ -13941,7 +14098,7 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
13941
14098
|
// StrategicThemeProperties: StrategicTheme entity.
|
|
13942
14099
|
strategic_theme: {
|
|
13943
14100
|
owner: { type: "string", description: "Owning person or team. Promote to a `node_owned_by_person` edge if ownership must be queryable." },
|
|
13944
|
-
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
|
|
14101
|
+
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"' },
|
|
13945
14102
|
description: { type: "string", description: "Short narrative of this time-bound thrust within its pillar" },
|
|
13946
14103
|
scope: { type: "string", description: "What the theme explicitly includes or excludes" }
|
|
13947
14104
|
},
|
|
@@ -14060,7 +14217,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14060
14217
|
team: {
|
|
14061
14218
|
team_type: { type: "string", enum: ["product", "engineering", "design", "growth", "customer_success"], description: "Functional area of the team" },
|
|
14062
14219
|
size: { type: "number", description: "Number of people on the team" },
|
|
14063
|
-
mission: { type: "string", description: "Team's mission statement" }
|
|
14220
|
+
mission: { type: "string", description: "Team's mission statement" },
|
|
14221
|
+
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`." }
|
|
14064
14222
|
},
|
|
14065
14223
|
// TeamOkrProperties: TeamOkr entity.
|
|
14066
14224
|
team_okr: {
|
|
@@ -14386,6 +14544,8 @@ var UPG_PROPERTY_SCHEMA = {
|
|
|
14386
14544
|
},
|
|
14387
14545
|
// WorkspaceProperties: Workspace: a spatial thinking space for arranging entities.
|
|
14388
14546
|
workspace: {
|
|
14547
|
+
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." },
|
|
14548
|
+
presentation: { type: "object", description: "Advisory rendering intent for the layer as a whole. A consumer may ignore it entirely and still be conformant." },
|
|
14389
14549
|
visibility: { type: "string", enum: ["private", "shared", "public"], description: "Who can see this workspace" },
|
|
14390
14550
|
purpose: { type: "string", description: "Free-text description. Pairs with the closed-enum `workspace_purpose`." },
|
|
14391
14551
|
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." },
|
|
@@ -32591,6 +32751,9 @@ var NODE_KEY_ORDER = [
|
|
|
32591
32751
|
"external_tool",
|
|
32592
32752
|
"external_ref",
|
|
32593
32753
|
"external_id",
|
|
32754
|
+
"external_links",
|
|
32755
|
+
"created_at",
|
|
32756
|
+
"updated_at",
|
|
32594
32757
|
"sort_order",
|
|
32595
32758
|
"properties"
|
|
32596
32759
|
];
|
|
@@ -32873,7 +33036,7 @@ function checkHeaderSealText(text) {
|
|
|
32873
33036
|
}
|
|
32874
33037
|
|
|
32875
33038
|
// src/index.ts
|
|
32876
|
-
var UPG_VERSION = "0.
|
|
33039
|
+
var UPG_VERSION = "0.33.1";
|
|
32877
33040
|
var UPG_FORMAT_VERSION = "0.4.0";
|
|
32878
33041
|
var MARKDOWN_FORMAT_VERSION = "0.1";
|
|
32879
33042
|
var UPG_TYPES = getTypes();
|
|
@@ -32958,6 +33121,7 @@ export {
|
|
|
32958
33121
|
LEGACY_PRODUCT_STAGES,
|
|
32959
33122
|
MARKDOWN_FORMAT_VERSION,
|
|
32960
33123
|
MARKET_COMPETITIVE_PLAYBOOK,
|
|
33124
|
+
NODE_KEY_ORDER,
|
|
32961
33125
|
OPERATIONS_QUALITY_PLAYBOOK,
|
|
32962
33126
|
PRESENT_UNDER_PROPERTY,
|
|
32963
33127
|
PRODUCT_DELIVERY_PLAYBOOK,
|