@company-semantics/contracts 13.19.1 → 14.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 +4 -4
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +66 -87
- package/src/generated/openapi-routes.ts +1 -0
- package/src/identity/index.ts +0 -4
- package/src/identity/people-org-chart.ts +11 -26
- package/src/index.ts +13 -4
- package/src/org/__tests__/org-units.test.ts +1 -4
- package/src/org/index.ts +13 -0
- package/src/org/schemas.ts +15 -12
- package/src/org/structure-facts.ts +105 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@company-semantics/contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -119,15 +119,15 @@
|
|
|
119
119
|
"zod": "^4.4.3"
|
|
120
120
|
},
|
|
121
121
|
"devDependencies": {
|
|
122
|
-
"@types/node": "^
|
|
122
|
+
"@types/node": "^25.9.3",
|
|
123
123
|
"husky": "^9.1.7",
|
|
124
124
|
"lint-staged": "^17.0.7",
|
|
125
125
|
"markdownlint-cli2": "^0.22.1",
|
|
126
126
|
"openapi-typescript": "^7.13.0",
|
|
127
|
-
"prettier": "^3.8.
|
|
127
|
+
"prettier": "^3.8.4",
|
|
128
128
|
"tsx": "^4.22.4",
|
|
129
129
|
"typescript": "^5.8.3",
|
|
130
|
-
"vitest": "^4.1.
|
|
130
|
+
"vitest": "^4.1.9",
|
|
131
131
|
"yaml": "^2.9.0"
|
|
132
132
|
},
|
|
133
133
|
"pnpm": {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
|
|
2
|
-
export const SPEC_HASH = '
|
|
3
|
-
export const SPEC_HASH_FULL = '
|
|
2
|
+
export const SPEC_HASH = '16c3b86cc35a' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = '16c3b86cc35a3f329766b702414438de4950430991a603372f1335206c96b600' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -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;
|
|
@@ -4051,6 +4050,11 @@ export interface components {
|
|
|
4051
4050
|
state: "allowed" | "blocked";
|
|
4052
4051
|
requiredScope?: string;
|
|
4053
4052
|
};
|
|
4053
|
+
manageSharing: {
|
|
4054
|
+
/** @enum {string} */
|
|
4055
|
+
state: "allowed" | "blocked";
|
|
4056
|
+
requiredScope?: string;
|
|
4057
|
+
};
|
|
4054
4058
|
};
|
|
4055
4059
|
workItem?: {
|
|
4056
4060
|
transferOwnership: {
|
|
@@ -4058,6 +4062,18 @@ export interface components {
|
|
|
4058
4062
|
state: "allowed" | "blocked";
|
|
4059
4063
|
requiredScope?: string;
|
|
4060
4064
|
};
|
|
4065
|
+
manageSharing: {
|
|
4066
|
+
/** @enum {string} */
|
|
4067
|
+
state: "allowed" | "blocked";
|
|
4068
|
+
requiredScope?: string;
|
|
4069
|
+
};
|
|
4070
|
+
};
|
|
4071
|
+
meetingRecording?: {
|
|
4072
|
+
manageSharing: {
|
|
4073
|
+
/** @enum {string} */
|
|
4074
|
+
state: "allowed" | "blocked";
|
|
4075
|
+
requiredScope?: string;
|
|
4076
|
+
};
|
|
4061
4077
|
};
|
|
4062
4078
|
};
|
|
4063
4079
|
};
|
|
@@ -4305,8 +4321,6 @@ export interface components {
|
|
|
4305
4321
|
count: number;
|
|
4306
4322
|
userIds: string[];
|
|
4307
4323
|
} | null;
|
|
4308
|
-
headUserId: string | null;
|
|
4309
|
-
headOrigin: ("user" | "inferred") | null;
|
|
4310
4324
|
}[];
|
|
4311
4325
|
};
|
|
4312
4326
|
OrgUnitDescendantsResponse: {
|
|
@@ -4455,8 +4469,6 @@ export interface components {
|
|
|
4455
4469
|
count: number;
|
|
4456
4470
|
userIds: string[];
|
|
4457
4471
|
} | null;
|
|
4458
|
-
headUserId: string | null;
|
|
4459
|
-
headOrigin: ("user" | "inferred") | null;
|
|
4460
4472
|
}[];
|
|
4461
4473
|
levelConfig: {
|
|
4462
4474
|
/** Format: uuid */
|
|
@@ -4601,12 +4613,6 @@ export interface components {
|
|
|
4601
4613
|
revokedAt: string | null;
|
|
4602
4614
|
reason: string | null;
|
|
4603
4615
|
};
|
|
4604
|
-
UnitHeadResponse: {
|
|
4605
|
-
/** Format: uuid */
|
|
4606
|
-
unitId: string;
|
|
4607
|
-
headUserId: string | null;
|
|
4608
|
-
origin: ("user" | "inferred") | null;
|
|
4609
|
-
};
|
|
4610
4616
|
OrgUnitMyAuthorityResponse: {
|
|
4611
4617
|
/** Format: uuid */
|
|
4612
4618
|
unitId: string;
|
|
@@ -4676,6 +4682,7 @@ export interface components {
|
|
|
4676
4682
|
/** @enum {string} */
|
|
4677
4683
|
status: "open" | "hiring" | "filled" | "closed";
|
|
4678
4684
|
filledByUserId: string | null;
|
|
4685
|
+
reportsToUserId: string | null;
|
|
4679
4686
|
createdAt: string;
|
|
4680
4687
|
updatedAt: string;
|
|
4681
4688
|
}[];
|
|
@@ -4693,6 +4700,7 @@ export interface components {
|
|
|
4693
4700
|
/** @enum {string} */
|
|
4694
4701
|
status: "open" | "hiring" | "filled" | "closed";
|
|
4695
4702
|
filledByUserId: string | null;
|
|
4703
|
+
reportsToUserId: string | null;
|
|
4696
4704
|
createdAt: string;
|
|
4697
4705
|
updatedAt: string;
|
|
4698
4706
|
};
|
|
@@ -4720,14 +4728,7 @@ export interface components {
|
|
|
4720
4728
|
/** Format: uuid */
|
|
4721
4729
|
unitId: string;
|
|
4722
4730
|
title: string | null;
|
|
4723
|
-
|
|
4724
|
-
unitHeads: {
|
|
4725
|
-
/** Format: uuid */
|
|
4726
|
-
unitId: string;
|
|
4727
|
-
/** Format: uuid */
|
|
4728
|
-
headUserId: string;
|
|
4729
|
-
/** @enum {string} */
|
|
4730
|
-
origin: "user" | "inferred";
|
|
4731
|
+
reportsToUserId: string | null;
|
|
4731
4732
|
}[];
|
|
4732
4733
|
};
|
|
4733
4734
|
/** @description Polling snapshot of a generic ingestion operation. */
|
|
@@ -8480,57 +8481,6 @@ export interface operations {
|
|
|
8480
8481
|
};
|
|
8481
8482
|
};
|
|
8482
8483
|
};
|
|
8483
|
-
setOrgUnitHead: {
|
|
8484
|
-
parameters: {
|
|
8485
|
-
query?: never;
|
|
8486
|
-
header?: never;
|
|
8487
|
-
path: {
|
|
8488
|
-
unitId: string;
|
|
8489
|
-
};
|
|
8490
|
-
cookie?: never;
|
|
8491
|
-
};
|
|
8492
|
-
requestBody: {
|
|
8493
|
-
content: {
|
|
8494
|
-
"application/json": {
|
|
8495
|
-
/** Format: uuid */
|
|
8496
|
-
userId: string;
|
|
8497
|
-
};
|
|
8498
|
-
};
|
|
8499
|
-
};
|
|
8500
|
-
responses: {
|
|
8501
|
-
/** @description The pinned unit head */
|
|
8502
|
-
200: {
|
|
8503
|
-
headers: {
|
|
8504
|
-
[name: string]: unknown;
|
|
8505
|
-
};
|
|
8506
|
-
content: {
|
|
8507
|
-
"application/json": components["schemas"]["UnitHeadResponse"];
|
|
8508
|
-
};
|
|
8509
|
-
};
|
|
8510
|
-
};
|
|
8511
|
-
};
|
|
8512
|
-
revertOrgUnitHead: {
|
|
8513
|
-
parameters: {
|
|
8514
|
-
query?: never;
|
|
8515
|
-
header?: never;
|
|
8516
|
-
path: {
|
|
8517
|
-
unitId: string;
|
|
8518
|
-
};
|
|
8519
|
-
cookie?: never;
|
|
8520
|
-
};
|
|
8521
|
-
requestBody?: never;
|
|
8522
|
-
responses: {
|
|
8523
|
-
/** @description The re-inferred unit head (or null when none resolves) */
|
|
8524
|
-
200: {
|
|
8525
|
-
headers: {
|
|
8526
|
-
[name: string]: unknown;
|
|
8527
|
-
};
|
|
8528
|
-
content: {
|
|
8529
|
-
"application/json": components["schemas"]["UnitHeadResponse"];
|
|
8530
|
-
};
|
|
8531
|
-
};
|
|
8532
|
-
};
|
|
8533
|
-
};
|
|
8534
8484
|
getMyOrgUnitAuthority: {
|
|
8535
8485
|
parameters: {
|
|
8536
8486
|
query?: never;
|
|
@@ -8777,6 +8727,35 @@ export interface operations {
|
|
|
8777
8727
|
};
|
|
8778
8728
|
};
|
|
8779
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
|
+
};
|
|
8780
8759
|
getPeopleOrgChart: {
|
|
8781
8760
|
parameters: {
|
|
8782
8761
|
query?: never;
|
|
@@ -89,6 +89,7 @@ export const openApiRoutes = {
|
|
|
89
89
|
'/api/org-units/{unitId}/open-roles': ['GET', 'POST'],
|
|
90
90
|
'/api/org-units/{unitId}/open-roles/{roleId}': ['PATCH'],
|
|
91
91
|
'/api/org-units/{unitId}/open-roles/{roleId}/fill': ['POST'],
|
|
92
|
+
'/api/org-units/{unitId}/open-roles/{roleId}/reports-to': ['PUT'],
|
|
92
93
|
'/api/org-units/{unitId}/owners': ['GET'],
|
|
93
94
|
'/api/org-units/{unitId}/permissions': ['GET'],
|
|
94
95
|
'/api/org-units/{unitId}/relationships': ['GET', 'POST'],
|
package/src/identity/index.ts
CHANGED
|
@@ -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
|
|
56
|
-
//
|
|
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
|
|
|
@@ -358,6 +354,19 @@ export {
|
|
|
358
354
|
// View authorization types (Phase 5 - ADR-APP-013)
|
|
359
355
|
export type { AuthorizableView } from "./org/index";
|
|
360
356
|
|
|
357
|
+
// Org structure facts: provenance + home/membership separation (ADR-CONTRACTS-066)
|
|
358
|
+
export {
|
|
359
|
+
FactSourceTierSchema,
|
|
360
|
+
FACT_SOURCE_TIER_PRECEDENCE,
|
|
361
|
+
FactProvenanceSchema,
|
|
362
|
+
HomeAssignmentSchema,
|
|
363
|
+
} from "./org/index";
|
|
364
|
+
export type {
|
|
365
|
+
FactSourceTier,
|
|
366
|
+
FactProvenance,
|
|
367
|
+
HomeAssignment,
|
|
368
|
+
} from "./org/index";
|
|
369
|
+
|
|
361
370
|
// Authority & Delegation vocabulary
|
|
362
371
|
export {
|
|
363
372
|
AuthoritySourceSchema,
|
|
@@ -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,
|
package/src/org/index.ts
CHANGED
|
@@ -87,6 +87,19 @@ export type {
|
|
|
87
87
|
export type { AuthorizableView } from "./view-scopes";
|
|
88
88
|
export { VIEW_SCOPE_MAP, getViewScope } from "./view-scopes";
|
|
89
89
|
|
|
90
|
+
// Org structure facts: provenance + home/membership separation (ADR-CONTRACTS-066)
|
|
91
|
+
export {
|
|
92
|
+
FactSourceTierSchema,
|
|
93
|
+
FACT_SOURCE_TIER_PRECEDENCE,
|
|
94
|
+
FactProvenanceSchema,
|
|
95
|
+
HomeAssignmentSchema,
|
|
96
|
+
} from "./structure-facts";
|
|
97
|
+
export type {
|
|
98
|
+
FactSourceTier,
|
|
99
|
+
FactProvenance,
|
|
100
|
+
HomeAssignment,
|
|
101
|
+
} from "./structure-facts";
|
|
102
|
+
|
|
90
103
|
// Canonical OrgUnit tree ordering (PRD-00506)
|
|
91
104
|
export type { TreeOrderableNode } from "./tree-ordering";
|
|
92
105
|
export { orderTreeNodes } from "./tree-ordering";
|
package/src/org/schemas.ts
CHANGED
|
@@ -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
|
});
|
|
@@ -1438,16 +1432,25 @@ export const CapabilitiesResponseSchema = z.object({
|
|
|
1438
1432
|
orgUnit: z.object({
|
|
1439
1433
|
manage: ActionCapabilitySchema,
|
|
1440
1434
|
}),
|
|
1441
|
-
// Entity ownership-transfer overrides (backend ADR-BE-259)
|
|
1442
|
-
//
|
|
1435
|
+
// Entity ownership-transfer overrides (backend ADR-BE-259) +
|
|
1436
|
+
// sharing-management custody (backend ADR-BE-288). Optional so older
|
|
1437
|
+
// backends that don't derive them still validate. manageSharing gates the
|
|
1438
|
+
// admin Share affordance on content the admin may not be able to view.
|
|
1443
1439
|
companyMd: z
|
|
1444
1440
|
.object({
|
|
1445
1441
|
transferOwnership: ActionCapabilitySchema,
|
|
1442
|
+
manageSharing: ActionCapabilitySchema,
|
|
1446
1443
|
})
|
|
1447
1444
|
.optional(),
|
|
1448
1445
|
workItem: z
|
|
1449
1446
|
.object({
|
|
1450
1447
|
transferOwnership: ActionCapabilitySchema,
|
|
1448
|
+
manageSharing: ActionCapabilitySchema,
|
|
1449
|
+
})
|
|
1450
|
+
.optional(),
|
|
1451
|
+
meetingRecording: z
|
|
1452
|
+
.object({
|
|
1453
|
+
manageSharing: ActionCapabilitySchema,
|
|
1451
1454
|
})
|
|
1452
1455
|
.optional(),
|
|
1453
1456
|
})
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org structure facts: provenance envelope + the home/membership separation.
|
|
3
|
+
*
|
|
4
|
+
* The org model is several INDEPENDENT graphs — the org-unit tree, the reporting
|
|
5
|
+
* graph, the home assignment, and the membership graph (and, later, positions).
|
|
6
|
+
* This module is the shared vocabulary for two cross-cutting ideas in that model
|
|
7
|
+
* (see ADR-CONTRACTS-066 and control ADR-CTRL-159):
|
|
8
|
+
*
|
|
9
|
+
* 1. Every structural fact carries provenance ({@link FactProvenanceSchema}).
|
|
10
|
+
* 2. "Home" is a dedicated ≤1-per-person relation ({@link HomeAssignmentSchema}),
|
|
11
|
+
* kept SEPARATE from `org_unit_memberships` — not a `kind` flag on memberships.
|
|
12
|
+
*
|
|
13
|
+
* Principles encoded here (do not drift from these):
|
|
14
|
+
* - Membership / home is an INPUT to authority derivation, NOT authority itself.
|
|
15
|
+
* `home member of unit` does not mean `owns unit`.
|
|
16
|
+
* - Facts are immutable; corrections SUPERSEDE prior facts (`supersedesFactId`)
|
|
17
|
+
* rather than mutating in place. This leaves room for event-sourcing later
|
|
18
|
+
* without forcing it now.
|
|
19
|
+
* - A truth hierarchy decides which fact wins: user > sync > import > inferred.
|
|
20
|
+
* - `source` is an OPEN string so new origins (scim, workday, csv, org_chart,
|
|
21
|
+
* google, slack, …) need no schema change; `tier` is the closed precedence axis.
|
|
22
|
+
*/
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Provenance
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The precedence axis for a fact. Closed set, ordered highest-precedence first
|
|
31
|
+
* (see {@link FACT_SOURCE_TIER_PRECEDENCE}). The specific origin lives in the
|
|
32
|
+
* open `source` string; this enum is what comparison / "who wins" reasons over.
|
|
33
|
+
*/
|
|
34
|
+
export const FactSourceTierSchema = z.enum([
|
|
35
|
+
"user",
|
|
36
|
+
"sync",
|
|
37
|
+
"import",
|
|
38
|
+
"inferred",
|
|
39
|
+
]);
|
|
40
|
+
export type FactSourceTier = z.infer<typeof FactSourceTierSchema>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Truth hierarchy, highest precedence first. A writer at a lower tier must never
|
|
44
|
+
* supersede a fact recorded at a higher tier (e.g. an org-chart import must not
|
|
45
|
+
* overwrite a `user`-entered reporting edge).
|
|
46
|
+
*/
|
|
47
|
+
export const FACT_SOURCE_TIER_PRECEDENCE = [
|
|
48
|
+
"user",
|
|
49
|
+
"sync",
|
|
50
|
+
"import",
|
|
51
|
+
"inferred",
|
|
52
|
+
] as const satisfies ReadonlyArray<FactSourceTier>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Provenance envelope attached to a structural fact (reporting edge, org unit,
|
|
56
|
+
* membership, home assignment). The live value of any fact is the
|
|
57
|
+
* highest-precedence, non-superseded record.
|
|
58
|
+
*/
|
|
59
|
+
export const FactProvenanceSchema = z.object({
|
|
60
|
+
/** Precedence axis used by the truth hierarchy. */
|
|
61
|
+
tier: FactSourceTierSchema,
|
|
62
|
+
/**
|
|
63
|
+
* Specific origin — OPEN set, e.g. `manual` | `org_chart` | `scim` | `hris` |
|
|
64
|
+
* `csv` | `google` | `slack`. New origins slot in without a schema change.
|
|
65
|
+
*/
|
|
66
|
+
source: z.string().min(1),
|
|
67
|
+
/** Extractor/matcher certainty in [0,1]; `null` for human-entered facts. */
|
|
68
|
+
confidence: z.number().min(0).max(1).nullable(),
|
|
69
|
+
/**
|
|
70
|
+
* True once a human has corrected/confirmed the fact. A locked fact is sticky:
|
|
71
|
+
* lower-tier writers (imports, inference) must not supersede it.
|
|
72
|
+
*/
|
|
73
|
+
locked: z.boolean(),
|
|
74
|
+
/**
|
|
75
|
+
* The fact this record supersedes (immutable correction history); `null`/absent
|
|
76
|
+
* for an original. Enables append+supersede without destructive updates.
|
|
77
|
+
*/
|
|
78
|
+
supersedesFactId: z.string().uuid().nullable().optional(),
|
|
79
|
+
});
|
|
80
|
+
export type FactProvenance = z.infer<typeof FactProvenanceSchema>;
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// HomeAssignment (separate from membership)
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A person's single structural home unit — at most one per person per org.
|
|
88
|
+
*
|
|
89
|
+
* SEPARATE relation from `org_unit_memberships` (NOT a `kind`/role flag on a
|
|
90
|
+
* shared table): the cardinalities differ on purpose — exactly one home, many
|
|
91
|
+
* memberships — which is the tell that they are different concepts. A shared
|
|
92
|
+
* `Membership(kind='home'|'member')` would inevitably grow
|
|
93
|
+
* `acting-home`/`temporary-home`/`future-home` flags.
|
|
94
|
+
*
|
|
95
|
+
* Home is an INPUT to authority derivation, sets `department`/`org` scope, and
|
|
96
|
+
* filters chart inclusion. It is NOT authority by itself (see Principles above).
|
|
97
|
+
* Supersedes the legacy `users.primary_unit_id` pointer (ADR-BE-120).
|
|
98
|
+
*/
|
|
99
|
+
export const HomeAssignmentSchema = z.object({
|
|
100
|
+
personId: z.string().uuid(),
|
|
101
|
+
orgUnitId: z.string().uuid(),
|
|
102
|
+
/** Provenance of this home assignment. Optional on lightweight projections. */
|
|
103
|
+
provenance: FactProvenanceSchema.optional(),
|
|
104
|
+
});
|
|
105
|
+
export type HomeAssignment = z.infer<typeof HomeAssignmentSchema>;
|