@company-semantics/contracts 13.20.0 → 15.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "13.20.0",
3
+ "version": "15.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -119,7 +119,7 @@
119
119
  "zod": "^4.4.3"
120
120
  },
121
121
  "devDependencies": {
122
- "@types/node": "^25.9.3",
122
+ "@types/node": "^22.10.0",
123
123
  "husky": "^9.1.7",
124
124
  "lint-staged": "^17.0.7",
125
125
  "markdownlint-cli2": "^0.22.1",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '255ee9f997af' as const;
3
- export const SPEC_HASH_FULL = '255ee9f997afb36f3e96cdcffb8aa30a97e5c14f8c21b884974c75f9d126d596' as const;
2
+ export const SPEC_HASH = '16c3b86cc35a' as const;
3
+ export const SPEC_HASH_FULL = '16c3b86cc35a3f329766b702414438de4950430991a603372f1335206c96b600' as const;
@@ -1972,24 +1972,6 @@ export interface paths {
1972
1972
  patch?: never;
1973
1973
  trace?: never;
1974
1974
  };
1975
- "/api/org-units/{unitId}/head": {
1976
- parameters: {
1977
- query?: never;
1978
- header?: never;
1979
- path?: never;
1980
- cookie?: never;
1981
- };
1982
- get?: never;
1983
- /** Pin an explicit head (anchor person) for an org unit */
1984
- put: operations["setOrgUnitHead"];
1985
- post?: never;
1986
- /** Revert an org unit head to the system-inferred default */
1987
- delete: operations["revertOrgUnitHead"];
1988
- options?: never;
1989
- head?: never;
1990
- patch?: never;
1991
- trace?: never;
1992
- };
1993
1975
  "/api/org-units/{unitId}/my-authority": {
1994
1976
  parameters: {
1995
1977
  query?: never;
@@ -2111,6 +2093,23 @@ export interface paths {
2111
2093
  patch?: never;
2112
2094
  trace?: never;
2113
2095
  };
2096
+ "/api/org-units/{unitId}/open-roles/{roleId}/reports-to": {
2097
+ parameters: {
2098
+ query?: never;
2099
+ header?: never;
2100
+ path?: never;
2101
+ cookie?: never;
2102
+ };
2103
+ get?: never;
2104
+ /** Set or clear an open role's own reporting edge */
2105
+ put: operations["setOrgUnitOpenRoleReportsTo"];
2106
+ post?: never;
2107
+ delete?: never;
2108
+ options?: never;
2109
+ head?: never;
2110
+ patch?: never;
2111
+ trace?: never;
2112
+ };
2114
2113
  "/api/users/org-chart": {
2115
2114
  parameters: {
2116
2115
  query?: never;
@@ -4322,8 +4321,6 @@ export interface components {
4322
4321
  count: number;
4323
4322
  userIds: string[];
4324
4323
  } | null;
4325
- headUserId: string | null;
4326
- headOrigin: ("user" | "inferred") | null;
4327
4324
  }[];
4328
4325
  };
4329
4326
  OrgUnitDescendantsResponse: {
@@ -4472,8 +4469,6 @@ export interface components {
4472
4469
  count: number;
4473
4470
  userIds: string[];
4474
4471
  } | null;
4475
- headUserId: string | null;
4476
- headOrigin: ("user" | "inferred") | null;
4477
4472
  }[];
4478
4473
  levelConfig: {
4479
4474
  /** Format: uuid */
@@ -4618,12 +4613,6 @@ export interface components {
4618
4613
  revokedAt: string | null;
4619
4614
  reason: string | null;
4620
4615
  };
4621
- UnitHeadResponse: {
4622
- /** Format: uuid */
4623
- unitId: string;
4624
- headUserId: string | null;
4625
- origin: ("user" | "inferred") | null;
4626
- };
4627
4616
  OrgUnitMyAuthorityResponse: {
4628
4617
  /** Format: uuid */
4629
4618
  unitId: string;
@@ -4693,6 +4682,7 @@ export interface components {
4693
4682
  /** @enum {string} */
4694
4683
  status: "open" | "hiring" | "filled" | "closed";
4695
4684
  filledByUserId: string | null;
4685
+ reportsToUserId: string | null;
4696
4686
  createdAt: string;
4697
4687
  updatedAt: string;
4698
4688
  }[];
@@ -4710,6 +4700,7 @@ export interface components {
4710
4700
  /** @enum {string} */
4711
4701
  status: "open" | "hiring" | "filled" | "closed";
4712
4702
  filledByUserId: string | null;
4703
+ reportsToUserId: string | null;
4713
4704
  createdAt: string;
4714
4705
  updatedAt: string;
4715
4706
  };
@@ -4737,14 +4728,7 @@ export interface components {
4737
4728
  /** Format: uuid */
4738
4729
  unitId: string;
4739
4730
  title: string | null;
4740
- }[];
4741
- unitHeads: {
4742
- /** Format: uuid */
4743
- unitId: string;
4744
- /** Format: uuid */
4745
- headUserId: string;
4746
- /** @enum {string} */
4747
- origin: "user" | "inferred";
4731
+ reportsToUserId: string | null;
4748
4732
  }[];
4749
4733
  };
4750
4734
  /** @description Polling snapshot of a generic ingestion operation. */
@@ -8497,57 +8481,6 @@ export interface operations {
8497
8481
  };
8498
8482
  };
8499
8483
  };
8500
- setOrgUnitHead: {
8501
- parameters: {
8502
- query?: never;
8503
- header?: never;
8504
- path: {
8505
- unitId: string;
8506
- };
8507
- cookie?: never;
8508
- };
8509
- requestBody: {
8510
- content: {
8511
- "application/json": {
8512
- /** Format: uuid */
8513
- userId: string;
8514
- };
8515
- };
8516
- };
8517
- responses: {
8518
- /** @description The pinned unit head */
8519
- 200: {
8520
- headers: {
8521
- [name: string]: unknown;
8522
- };
8523
- content: {
8524
- "application/json": components["schemas"]["UnitHeadResponse"];
8525
- };
8526
- };
8527
- };
8528
- };
8529
- revertOrgUnitHead: {
8530
- parameters: {
8531
- query?: never;
8532
- header?: never;
8533
- path: {
8534
- unitId: string;
8535
- };
8536
- cookie?: never;
8537
- };
8538
- requestBody?: never;
8539
- responses: {
8540
- /** @description The re-inferred unit head (or null when none resolves) */
8541
- 200: {
8542
- headers: {
8543
- [name: string]: unknown;
8544
- };
8545
- content: {
8546
- "application/json": components["schemas"]["UnitHeadResponse"];
8547
- };
8548
- };
8549
- };
8550
- };
8551
8484
  getMyOrgUnitAuthority: {
8552
8485
  parameters: {
8553
8486
  query?: never;
@@ -8794,6 +8727,35 @@ export interface operations {
8794
8727
  };
8795
8728
  };
8796
8729
  };
8730
+ setOrgUnitOpenRoleReportsTo: {
8731
+ parameters: {
8732
+ query?: never;
8733
+ header?: never;
8734
+ path: {
8735
+ unitId: string;
8736
+ roleId: string;
8737
+ };
8738
+ cookie?: never;
8739
+ };
8740
+ requestBody: {
8741
+ content: {
8742
+ "application/json": {
8743
+ userId: string | null;
8744
+ };
8745
+ };
8746
+ };
8747
+ responses: {
8748
+ /** @description Open role with updated reporting edge */
8749
+ 200: {
8750
+ headers: {
8751
+ [name: string]: unknown;
8752
+ };
8753
+ content: {
8754
+ "application/json": components["schemas"]["OpenRoleResponse"];
8755
+ };
8756
+ };
8757
+ };
8758
+ };
8797
8759
  getPeopleOrgChart: {
8798
8760
  parameters: {
8799
8761
  query?: never;
@@ -70,7 +70,7 @@ export interface EmailPayloads {
70
70
  /** Name of the org unit / team the access applies to */
71
71
  unitName: string;
72
72
  /** Human-readable role label the recipient was given */
73
- roleLabel: "Leader" | "Delegate";
73
+ roleLabel: "Unit owner" | "Delegate";
74
74
  /** Full URL to view the org unit in the app */
75
75
  ctaUrl: string;
76
76
  /** Optional message from the granter, shown in the email and recorded with the grant */
@@ -82,7 +82,6 @@ export const openApiRoutes = {
82
82
  '/api/org-units/{unitId}/delegations': ['POST'],
83
83
  '/api/org-units/{unitId}/delegations/{id}': ['DELETE', 'PATCH'],
84
84
  '/api/org-units/{unitId}/descendants': ['GET'],
85
- '/api/org-units/{unitId}/head': ['DELETE', 'PUT'],
86
85
  '/api/org-units/{unitId}/memberships': ['GET', 'POST'],
87
86
  '/api/org-units/{unitId}/memberships/{userId}': ['DELETE'],
88
87
  '/api/org-units/{unitId}/memberships/{userId}/role': ['PUT'],
@@ -90,6 +89,7 @@ export const openApiRoutes = {
90
89
  '/api/org-units/{unitId}/open-roles': ['GET', 'POST'],
91
90
  '/api/org-units/{unitId}/open-roles/{roleId}': ['PATCH'],
92
91
  '/api/org-units/{unitId}/open-roles/{roleId}/fill': ['POST'],
92
+ '/api/org-units/{unitId}/open-roles/{roleId}/reports-to': ['PUT'],
93
93
  '/api/org-units/{unitId}/owners': ['GET'],
94
94
  '/api/org-units/{unitId}/permissions': ['GET'],
95
95
  '/api/org-units/{unitId}/relationships': ['GET', 'POST'],
@@ -67,8 +67,6 @@ export {
67
67
  PeopleOrgChartNodeSchema,
68
68
  PeopleOrgChartEdgeSchema,
69
69
  PeopleOrgChartOpenRoleSchema,
70
- UnitHeadOriginSchema,
71
- PeopleOrgChartUnitHeadSchema,
72
70
  PeopleOrgChartResponseSchema,
73
71
  } from "./people-org-chart";
74
72
  export type {
@@ -76,7 +74,5 @@ export type {
76
74
  PeopleOrgChartNode,
77
75
  PeopleOrgChartEdge,
78
76
  PeopleOrgChartOpenRole,
79
- UnitHeadOrigin,
80
- PeopleOrgChartUnitHead,
81
77
  PeopleOrgChartResponse,
82
78
  } from "./people-org-chart";
@@ -52,52 +52,37 @@ export type PeopleOrgChartEdge = z.infer<typeof PeopleOrgChartEdgeSchema>;
52
52
  // Open role — a persisted placeholder seat rendered as a dashed "Open role"
53
53
  // card in its unit's sibling column. Only active roles (open + hiring) are
54
54
  // included; filled/closed roles never appear here. `unitId` seats the card in
55
- // the right column and resolves its attach-person, mirroring how empty-unit
56
- // placeholders and pending-invite ghosts are positioned.
55
+ // the right column. `reportsToUserId` is the open role's OWN reporting edge
56
+ // (ADR-BE-291): it defaults to the role's creator and is editable/clearable, so
57
+ // a person-less seat is placed by reporting like everyone else (the chart owns
58
+ // placement, ADR-CTRL-159). Null → the role hangs in its unit's projection-only
59
+ // Unassigned bucket. This replaces the retired unit-head anchor (ADR-BE-284).
57
60
  // ---------------------------------------------------------------------------
58
61
 
59
62
  export const PeopleOrgChartOpenRoleSchema = z.object({
60
63
  id: z.string().uuid(),
61
64
  unitId: z.string().uuid(),
62
65
  title: z.string().nullable(),
66
+ reportsToUserId: z.string().uuid().nullable(),
63
67
  });
64
68
 
65
69
  export type PeopleOrgChartOpenRole = z.infer<
66
70
  typeof PeopleOrgChartOpenRoleSchema
67
71
  >;
68
72
 
69
- // ---------------------------------------------------------------------------
70
- // Unit head — the person a unit (and its person-less children: empty-unit
71
- // placeholders, pending-invite ghosts, open-role seats) anchors under in the
72
- // people chart. A presentation/anchor "tether" (ADR-BE-284), NOT authority.
73
- // `origin` distinguishes an explicit user choice from a system-inferred default
74
- // so the UI can render "Pinned" vs "Auto" and offer revert-to-auto. The client
75
- // prefers the head of a unit's PARENT when anchoring its person-less children,
76
- // falling back to the render-time heuristic only when no head row exists.
77
- // ---------------------------------------------------------------------------
78
-
79
- export const UnitHeadOriginSchema = z.enum(["user", "inferred"]);
80
- export type UnitHeadOrigin = z.infer<typeof UnitHeadOriginSchema>;
81
-
82
- export const PeopleOrgChartUnitHeadSchema = z.object({
83
- unitId: z.string().uuid(),
84
- headUserId: z.string().uuid(),
85
- origin: UnitHeadOriginSchema,
86
- });
87
-
88
- export type PeopleOrgChartUnitHead = z.infer<
89
- typeof PeopleOrgChartUnitHeadSchema
90
- >;
91
-
92
73
  // ---------------------------------------------------------------------------
93
74
  // GET /api/users/org-chart
94
75
  // ---------------------------------------------------------------------------
76
+ //
77
+ // Person-less cards (empty-unit placeholders, ghosts, open roles) no longer
78
+ // anchor under a stored unit-head "tether" (ADR-BE-284, retired by ADR-BE-292).
79
+ // Open roles carry their own `reportsToUserId` edge; placeholders fall back to
80
+ // the render-time reporting heuristic.
95
81
 
96
82
  export const PeopleOrgChartResponseSchema = z.object({
97
83
  nodes: z.array(PeopleOrgChartNodeSchema),
98
84
  edges: z.array(PeopleOrgChartEdgeSchema),
99
85
  openRoles: z.array(PeopleOrgChartOpenRoleSchema),
100
- unitHeads: z.array(PeopleOrgChartUnitHeadSchema),
101
86
  });
102
87
 
103
88
  export type PeopleOrgChartResponse = z.infer<
package/src/index.ts CHANGED
@@ -173,8 +173,6 @@ export {
173
173
  PeopleOrgChartNodeSchema,
174
174
  PeopleOrgChartEdgeSchema,
175
175
  PeopleOrgChartOpenRoleSchema,
176
- UnitHeadOriginSchema,
177
- PeopleOrgChartUnitHeadSchema,
178
176
  PeopleOrgChartResponseSchema,
179
177
  } from "./identity/index";
180
178
  export type {
@@ -182,8 +180,6 @@ export type {
182
180
  PeopleOrgChartNode,
183
181
  PeopleOrgChartEdge,
184
182
  PeopleOrgChartOpenRole,
185
- UnitHeadOrigin,
186
- PeopleOrgChartUnitHead,
187
183
  PeopleOrgChartResponse,
188
184
  } from "./identity/index";
189
185
 
@@ -98,8 +98,6 @@ describe("OrgUnitTreeNodeSchema", () => {
98
98
  memberCount: 5,
99
99
  openRoleCount: 1,
100
100
  missingAtNextLevel: null,
101
- headUserId: null,
102
- headOrigin: null,
103
101
  };
104
102
  expect(() => OrgUnitTreeNodeSchema.parse(base)).not.toThrow();
105
103
  expect(() => OrgUnitTreeNodeSchema.parse({ ...base, depth: 0 })).toThrow();
@@ -114,8 +112,6 @@ describe("OrgUnitTreeNodeSchema", () => {
114
112
  memberCount: 5,
115
113
  openRoleCount: 0,
116
114
  missingAtNextLevel: null,
117
- headUserId: null,
118
- headOrigin: null,
119
115
  };
120
116
  expect(() => OrgUnitTreeNodeSchema.parse(base)).not.toThrow();
121
117
  expect(() =>
@@ -133,6 +129,7 @@ describe("OpenRoleSchema", () => {
133
129
  targetStartDate: "2026-09-01",
134
130
  status: "open",
135
131
  filledByUserId: null,
132
+ reportsToUserId: null,
136
133
  createdAt: "2026-04-17T00:00:00Z",
137
134
  updatedAt: "2026-04-17T00:00:00Z",
138
135
  ...overrides,
@@ -949,16 +949,6 @@ export const OrgUnitTreeNodeSchema = OrgUnitSchema.extend({
949
949
  */
950
950
  openRoleCount: z.number().int().min(0),
951
951
  missingAtNextLevel: MissingAtNextLevelSchema.nullable(),
952
- /**
953
- * The unit head (ADR-BE-284): the person this unit (and its person-less
954
- * children) anchors under in the people chart — a presentation "tether", NOT
955
- * authority. `headUserId` is null when no head is resolved (e.g. an empty
956
- * unit with no members). `headOrigin` is `'user'` for an explicit choice,
957
- * `'inferred'` for a system default (so the management UI can show Pinned vs
958
- * Auto and offer revert-to-auto), or null when there is no head.
959
- */
960
- headUserId: z.string().uuid().nullable(),
961
- headOrigin: z.enum(["user", "inferred"]).nullable(),
962
952
  });
963
953
 
964
954
  export const OrgUnitMembershipSchema = z.object({
@@ -989,6 +979,10 @@ export const OpenRoleSchema = z.object({
989
979
  targetStartDate: z.string().nullable(),
990
980
  status: OpenRoleStatusSchema,
991
981
  filledByUserId: z.string().uuid().nullable(),
982
+ // The open role's own reporting edge (ADR-BE-291): defaults to its creator,
983
+ // editable/clearable. Drives chart placement now that the unit-head anchor is
984
+ // retired (ADR-BE-292). Null → unmanaged (Unassigned bucket).
985
+ reportsToUserId: z.string().uuid().nullable(),
992
986
  createdAt: z.string(),
993
987
  updatedAt: z.string(),
994
988
  });
@@ -44,7 +44,7 @@ export const OrgChartRoleSchema = z.enum(ORG_CHART_ROLES);
44
44
  */
45
45
  export const ORG_CHART_ROLE_LABELS: Record<OrgChartRole, string> = {
46
46
  owner: "CEO",
47
- leader: "Leader",
47
+ leader: "Unit owner",
48
48
  delegate: "Delegate",
49
49
  admin: "Admin",
50
50
  };