@company-semantics/contracts 56.0.0 → 57.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 +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +432 -0
- package/src/generated/openapi-routes.ts +8 -0
- package/src/index.ts +58 -0
- package/src/org/README.md +36 -0
- package/src/org/index.ts +38 -0
- package/src/org/schemas.ts +428 -0
- package/src/resource-key-types.ts +3 -0
- package/src/resource-keys.ts +2 -0
package/package.json
CHANGED
|
@@ -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 = 'ffeb6e367b38' as const;
|
|
3
|
+
export const SPEC_HASH_FULL = 'ffeb6e367b385ad32f7b46e4e74d276786d6cdf175352e57d0fc229dc8f506af' as const;
|
package/src/api/generated.ts
CHANGED
|
@@ -926,6 +926,40 @@ export interface paths {
|
|
|
926
926
|
patch?: never;
|
|
927
927
|
trace?: never;
|
|
928
928
|
};
|
|
929
|
+
"/api/workspace/invites/batch": {
|
|
930
|
+
parameters: {
|
|
931
|
+
query?: never;
|
|
932
|
+
header?: never;
|
|
933
|
+
path?: never;
|
|
934
|
+
cookie?: never;
|
|
935
|
+
};
|
|
936
|
+
get?: never;
|
|
937
|
+
put?: never;
|
|
938
|
+
/** Invite many directory people at once */
|
|
939
|
+
post: operations["batchInvite"];
|
|
940
|
+
delete?: never;
|
|
941
|
+
options?: never;
|
|
942
|
+
head?: never;
|
|
943
|
+
patch?: never;
|
|
944
|
+
trace?: never;
|
|
945
|
+
};
|
|
946
|
+
"/api/workspace/invites/{id}/reissue": {
|
|
947
|
+
parameters: {
|
|
948
|
+
query?: never;
|
|
949
|
+
header?: never;
|
|
950
|
+
path?: never;
|
|
951
|
+
cookie?: never;
|
|
952
|
+
};
|
|
953
|
+
get?: never;
|
|
954
|
+
put?: never;
|
|
955
|
+
/** Reissue an invite whose email was never delivered */
|
|
956
|
+
post: operations["reissueInvite"];
|
|
957
|
+
delete?: never;
|
|
958
|
+
options?: never;
|
|
959
|
+
head?: never;
|
|
960
|
+
patch?: never;
|
|
961
|
+
trace?: never;
|
|
962
|
+
};
|
|
929
963
|
"/api/workspace/invites/accept": {
|
|
930
964
|
parameters: {
|
|
931
965
|
query?: never;
|
|
@@ -960,6 +994,92 @@ export interface paths {
|
|
|
960
994
|
patch?: never;
|
|
961
995
|
trace?: never;
|
|
962
996
|
};
|
|
997
|
+
"/api/workspace/invite-batches": {
|
|
998
|
+
parameters: {
|
|
999
|
+
query?: never;
|
|
1000
|
+
header?: never;
|
|
1001
|
+
path?: never;
|
|
1002
|
+
cookie?: never;
|
|
1003
|
+
};
|
|
1004
|
+
get?: never;
|
|
1005
|
+
put?: never;
|
|
1006
|
+
/** Draft a reviewable invite batch */
|
|
1007
|
+
post: operations["createInviteBatch"];
|
|
1008
|
+
delete?: never;
|
|
1009
|
+
options?: never;
|
|
1010
|
+
head?: never;
|
|
1011
|
+
patch?: never;
|
|
1012
|
+
trace?: never;
|
|
1013
|
+
};
|
|
1014
|
+
"/api/workspace/invite-batches/{id}": {
|
|
1015
|
+
parameters: {
|
|
1016
|
+
query?: never;
|
|
1017
|
+
header?: never;
|
|
1018
|
+
path?: never;
|
|
1019
|
+
cookie?: never;
|
|
1020
|
+
};
|
|
1021
|
+
/** Review an invite batch */
|
|
1022
|
+
get: operations["getInviteBatch"];
|
|
1023
|
+
put?: never;
|
|
1024
|
+
post?: never;
|
|
1025
|
+
delete?: never;
|
|
1026
|
+
options?: never;
|
|
1027
|
+
head?: never;
|
|
1028
|
+
/** Change an invite batch selection or role */
|
|
1029
|
+
patch: operations["updateInviteBatch"];
|
|
1030
|
+
trace?: never;
|
|
1031
|
+
};
|
|
1032
|
+
"/api/workspace/invite-batches/{id}/prepare": {
|
|
1033
|
+
parameters: {
|
|
1034
|
+
query?: never;
|
|
1035
|
+
header?: never;
|
|
1036
|
+
path?: never;
|
|
1037
|
+
cookie?: never;
|
|
1038
|
+
};
|
|
1039
|
+
get?: never;
|
|
1040
|
+
put?: never;
|
|
1041
|
+
/** Evaluate eligibility and make a batch reviewable */
|
|
1042
|
+
post: operations["prepareInviteBatch"];
|
|
1043
|
+
delete?: never;
|
|
1044
|
+
options?: never;
|
|
1045
|
+
head?: never;
|
|
1046
|
+
patch?: never;
|
|
1047
|
+
trace?: never;
|
|
1048
|
+
};
|
|
1049
|
+
"/api/workspace/invite-batches/{id}/send": {
|
|
1050
|
+
parameters: {
|
|
1051
|
+
query?: never;
|
|
1052
|
+
header?: never;
|
|
1053
|
+
path?: never;
|
|
1054
|
+
cookie?: never;
|
|
1055
|
+
};
|
|
1056
|
+
get?: never;
|
|
1057
|
+
put?: never;
|
|
1058
|
+
/** Send a reviewed invite batch */
|
|
1059
|
+
post: operations["sendInviteBatch"];
|
|
1060
|
+
delete?: never;
|
|
1061
|
+
options?: never;
|
|
1062
|
+
head?: never;
|
|
1063
|
+
patch?: never;
|
|
1064
|
+
trace?: never;
|
|
1065
|
+
};
|
|
1066
|
+
"/api/workspace/invite-batches/{id}/cancel": {
|
|
1067
|
+
parameters: {
|
|
1068
|
+
query?: never;
|
|
1069
|
+
header?: never;
|
|
1070
|
+
path?: never;
|
|
1071
|
+
cookie?: never;
|
|
1072
|
+
};
|
|
1073
|
+
get?: never;
|
|
1074
|
+
put?: never;
|
|
1075
|
+
/** Cancel the unsent remainder of an invite batch */
|
|
1076
|
+
post: operations["cancelInviteBatch"];
|
|
1077
|
+
delete?: never;
|
|
1078
|
+
options?: never;
|
|
1079
|
+
head?: never;
|
|
1080
|
+
patch?: never;
|
|
1081
|
+
trace?: never;
|
|
1082
|
+
};
|
|
963
1083
|
"/api/workspace/invites/validate": {
|
|
964
1084
|
parameters: {
|
|
965
1085
|
query?: never;
|
|
@@ -977,6 +1097,23 @@ export interface paths {
|
|
|
977
1097
|
patch?: never;
|
|
978
1098
|
trace?: never;
|
|
979
1099
|
};
|
|
1100
|
+
"/api/workspace/directory": {
|
|
1101
|
+
parameters: {
|
|
1102
|
+
query?: never;
|
|
1103
|
+
header?: never;
|
|
1104
|
+
path?: never;
|
|
1105
|
+
cookie?: never;
|
|
1106
|
+
};
|
|
1107
|
+
/** List the org people directory */
|
|
1108
|
+
get: operations["listOrgDirectory"];
|
|
1109
|
+
put?: never;
|
|
1110
|
+
post?: never;
|
|
1111
|
+
delete?: never;
|
|
1112
|
+
options?: never;
|
|
1113
|
+
head?: never;
|
|
1114
|
+
patch?: never;
|
|
1115
|
+
trace?: never;
|
|
1116
|
+
};
|
|
980
1117
|
"/api/workspace/domains": {
|
|
981
1118
|
parameters: {
|
|
982
1119
|
query?: never;
|
|
@@ -4577,6 +4714,32 @@ export interface components {
|
|
|
4577
4714
|
contributesTo: string[];
|
|
4578
4715
|
};
|
|
4579
4716
|
};
|
|
4717
|
+
BatchInviteResponse: {
|
|
4718
|
+
invited: string[];
|
|
4719
|
+
skipped: {
|
|
4720
|
+
/** Format: uuid */
|
|
4721
|
+
personId: string;
|
|
4722
|
+
/** @enum {string} */
|
|
4723
|
+
reason: "already-member" | "invite-pending" | "no-email" | "send-failed";
|
|
4724
|
+
}[];
|
|
4725
|
+
failed: {
|
|
4726
|
+
/** Format: uuid */
|
|
4727
|
+
personId: string;
|
|
4728
|
+
/** @enum {string} */
|
|
4729
|
+
reason: "already-member" | "invite-pending" | "no-email" | "send-failed";
|
|
4730
|
+
}[];
|
|
4731
|
+
};
|
|
4732
|
+
BatchInviteRequest: {
|
|
4733
|
+
personIds: string[];
|
|
4734
|
+
/** @enum {string} */
|
|
4735
|
+
role: "admin" | "member";
|
|
4736
|
+
};
|
|
4737
|
+
ReissueInviteResponse: {
|
|
4738
|
+
/** Format: uuid */
|
|
4739
|
+
inviteId: string;
|
|
4740
|
+
/** Format: uuid */
|
|
4741
|
+
previousInviteId: string;
|
|
4742
|
+
};
|
|
4580
4743
|
InviteListResponse: {
|
|
4581
4744
|
id: string;
|
|
4582
4745
|
orgId: string;
|
|
@@ -4620,11 +4783,72 @@ export interface components {
|
|
|
4620
4783
|
AcceptInviteRequest: {
|
|
4621
4784
|
token: string;
|
|
4622
4785
|
};
|
|
4786
|
+
InviteBatchResponse: {
|
|
4787
|
+
batch: {
|
|
4788
|
+
/** Format: uuid */
|
|
4789
|
+
id: string;
|
|
4790
|
+
/** @enum {string} */
|
|
4791
|
+
status: "draft" | "ready" | "sending" | "completed" | "cancelled";
|
|
4792
|
+
source: string;
|
|
4793
|
+
revision: number;
|
|
4794
|
+
counts: {
|
|
4795
|
+
candidate: number;
|
|
4796
|
+
eligible: number;
|
|
4797
|
+
excluded: number;
|
|
4798
|
+
sent: number;
|
|
4799
|
+
failed: number;
|
|
4800
|
+
skipped: number;
|
|
4801
|
+
};
|
|
4802
|
+
/** Format: uuid */
|
|
4803
|
+
createdByUserId: string;
|
|
4804
|
+
confirmedByUserId: string | null;
|
|
4805
|
+
confirmedAt: string | null;
|
|
4806
|
+
createdAt: string;
|
|
4807
|
+
updatedAt: string;
|
|
4808
|
+
};
|
|
4809
|
+
items: {
|
|
4810
|
+
/** Format: uuid */
|
|
4811
|
+
personId: string;
|
|
4812
|
+
displayName: string | null;
|
|
4813
|
+
emailSnapshot: string | null;
|
|
4814
|
+
/** @enum {string} */
|
|
4815
|
+
role: "admin" | "member";
|
|
4816
|
+
/** @enum {string} */
|
|
4817
|
+
status: "eligible" | "excluded" | "sent" | "failed" | "skipped";
|
|
4818
|
+
reason: ("already-member" | "invite-pending" | "no-email" | "invalid-email" | "removed-by-reviewer" | "address-changed" | "person-removed" | "send-failed" | "inactive-in-hris") | null;
|
|
4819
|
+
inviteId: string | null;
|
|
4820
|
+
emailOutsideVerifiedDomain: boolean;
|
|
4821
|
+
}[];
|
|
4822
|
+
verifiedDomains: string[];
|
|
4823
|
+
};
|
|
4824
|
+
CreateInviteBatchRequest: {
|
|
4825
|
+
personIds: string[];
|
|
4826
|
+
/** @enum {string} */
|
|
4827
|
+
role: "admin" | "member";
|
|
4828
|
+
};
|
|
4829
|
+
UpdateInviteBatchRequest: {
|
|
4830
|
+
deselectPersonIds?: string[];
|
|
4831
|
+
reselectPersonIds?: string[];
|
|
4832
|
+
/** @enum {string} */
|
|
4833
|
+
role?: "admin" | "member";
|
|
4834
|
+
};
|
|
4835
|
+
SendInviteBatchRequest: {
|
|
4836
|
+
expectedRevision: number;
|
|
4837
|
+
};
|
|
4623
4838
|
InviteValidationResult: {
|
|
4624
4839
|
valid: boolean;
|
|
4625
4840
|
status: string;
|
|
4626
4841
|
orgName?: string;
|
|
4627
4842
|
};
|
|
4843
|
+
OrgDirectoryResponse: {
|
|
4844
|
+
people: {
|
|
4845
|
+
/** Format: uuid */
|
|
4846
|
+
personId: string;
|
|
4847
|
+
displayName: string;
|
|
4848
|
+
primaryEmail: string | null;
|
|
4849
|
+
userId: string | null;
|
|
4850
|
+
}[];
|
|
4851
|
+
};
|
|
4628
4852
|
DomainListResponse: {
|
|
4629
4853
|
id: string;
|
|
4630
4854
|
orgId: string;
|
|
@@ -8230,6 +8454,52 @@ export interface operations {
|
|
|
8230
8454
|
};
|
|
8231
8455
|
};
|
|
8232
8456
|
};
|
|
8457
|
+
batchInvite: {
|
|
8458
|
+
parameters: {
|
|
8459
|
+
query?: never;
|
|
8460
|
+
header?: never;
|
|
8461
|
+
path?: never;
|
|
8462
|
+
cookie?: never;
|
|
8463
|
+
};
|
|
8464
|
+
requestBody: {
|
|
8465
|
+
content: {
|
|
8466
|
+
"application/json": components["schemas"]["BatchInviteRequest"];
|
|
8467
|
+
};
|
|
8468
|
+
};
|
|
8469
|
+
responses: {
|
|
8470
|
+
/** @description Per-person outcome; partial success is a normal result */
|
|
8471
|
+
200: {
|
|
8472
|
+
headers: {
|
|
8473
|
+
[name: string]: unknown;
|
|
8474
|
+
};
|
|
8475
|
+
content: {
|
|
8476
|
+
"application/json": components["schemas"]["BatchInviteResponse"];
|
|
8477
|
+
};
|
|
8478
|
+
};
|
|
8479
|
+
};
|
|
8480
|
+
};
|
|
8481
|
+
reissueInvite: {
|
|
8482
|
+
parameters: {
|
|
8483
|
+
query?: never;
|
|
8484
|
+
header?: never;
|
|
8485
|
+
path: {
|
|
8486
|
+
id: string;
|
|
8487
|
+
};
|
|
8488
|
+
cookie?: never;
|
|
8489
|
+
};
|
|
8490
|
+
requestBody?: never;
|
|
8491
|
+
responses: {
|
|
8492
|
+
/** @description Prior invite revoked and a replacement sent */
|
|
8493
|
+
200: {
|
|
8494
|
+
headers: {
|
|
8495
|
+
[name: string]: unknown;
|
|
8496
|
+
};
|
|
8497
|
+
content: {
|
|
8498
|
+
"application/json": components["schemas"]["ReissueInviteResponse"];
|
|
8499
|
+
};
|
|
8500
|
+
};
|
|
8501
|
+
};
|
|
8502
|
+
};
|
|
8233
8503
|
acceptInvite: {
|
|
8234
8504
|
parameters: {
|
|
8235
8505
|
query?: never;
|
|
@@ -8274,6 +8544,148 @@ export interface operations {
|
|
|
8274
8544
|
};
|
|
8275
8545
|
};
|
|
8276
8546
|
};
|
|
8547
|
+
createInviteBatch: {
|
|
8548
|
+
parameters: {
|
|
8549
|
+
query?: never;
|
|
8550
|
+
header?: never;
|
|
8551
|
+
path?: never;
|
|
8552
|
+
cookie?: never;
|
|
8553
|
+
};
|
|
8554
|
+
requestBody: {
|
|
8555
|
+
content: {
|
|
8556
|
+
"application/json": components["schemas"]["CreateInviteBatchRequest"];
|
|
8557
|
+
};
|
|
8558
|
+
};
|
|
8559
|
+
responses: {
|
|
8560
|
+
/** @description Draft created; nothing has been sent */
|
|
8561
|
+
200: {
|
|
8562
|
+
headers: {
|
|
8563
|
+
[name: string]: unknown;
|
|
8564
|
+
};
|
|
8565
|
+
content: {
|
|
8566
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8567
|
+
};
|
|
8568
|
+
};
|
|
8569
|
+
};
|
|
8570
|
+
};
|
|
8571
|
+
getInviteBatch: {
|
|
8572
|
+
parameters: {
|
|
8573
|
+
query?: never;
|
|
8574
|
+
header?: never;
|
|
8575
|
+
path: {
|
|
8576
|
+
id: string;
|
|
8577
|
+
};
|
|
8578
|
+
cookie?: never;
|
|
8579
|
+
};
|
|
8580
|
+
requestBody?: never;
|
|
8581
|
+
responses: {
|
|
8582
|
+
/** @description Recipients, exclusions with reasons, and the org verified domains */
|
|
8583
|
+
200: {
|
|
8584
|
+
headers: {
|
|
8585
|
+
[name: string]: unknown;
|
|
8586
|
+
};
|
|
8587
|
+
content: {
|
|
8588
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8589
|
+
};
|
|
8590
|
+
};
|
|
8591
|
+
};
|
|
8592
|
+
};
|
|
8593
|
+
updateInviteBatch: {
|
|
8594
|
+
parameters: {
|
|
8595
|
+
query?: never;
|
|
8596
|
+
header?: never;
|
|
8597
|
+
path: {
|
|
8598
|
+
id: string;
|
|
8599
|
+
};
|
|
8600
|
+
cookie?: never;
|
|
8601
|
+
};
|
|
8602
|
+
requestBody: {
|
|
8603
|
+
content: {
|
|
8604
|
+
"application/json": components["schemas"]["UpdateInviteBatchRequest"];
|
|
8605
|
+
};
|
|
8606
|
+
};
|
|
8607
|
+
responses: {
|
|
8608
|
+
/** @description Applied; revision bumped, invalidating any open review */
|
|
8609
|
+
200: {
|
|
8610
|
+
headers: {
|
|
8611
|
+
[name: string]: unknown;
|
|
8612
|
+
};
|
|
8613
|
+
content: {
|
|
8614
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8615
|
+
};
|
|
8616
|
+
};
|
|
8617
|
+
};
|
|
8618
|
+
};
|
|
8619
|
+
prepareInviteBatch: {
|
|
8620
|
+
parameters: {
|
|
8621
|
+
query?: never;
|
|
8622
|
+
header?: never;
|
|
8623
|
+
path: {
|
|
8624
|
+
id: string;
|
|
8625
|
+
};
|
|
8626
|
+
cookie?: never;
|
|
8627
|
+
};
|
|
8628
|
+
requestBody?: never;
|
|
8629
|
+
responses: {
|
|
8630
|
+
/** @description Eligibility evaluated; batch is ready to review */
|
|
8631
|
+
200: {
|
|
8632
|
+
headers: {
|
|
8633
|
+
[name: string]: unknown;
|
|
8634
|
+
};
|
|
8635
|
+
content: {
|
|
8636
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8637
|
+
};
|
|
8638
|
+
};
|
|
8639
|
+
};
|
|
8640
|
+
};
|
|
8641
|
+
sendInviteBatch: {
|
|
8642
|
+
parameters: {
|
|
8643
|
+
query?: never;
|
|
8644
|
+
header?: never;
|
|
8645
|
+
path: {
|
|
8646
|
+
id: string;
|
|
8647
|
+
};
|
|
8648
|
+
cookie?: never;
|
|
8649
|
+
};
|
|
8650
|
+
requestBody: {
|
|
8651
|
+
content: {
|
|
8652
|
+
"application/json": components["schemas"]["SendInviteBatchRequest"];
|
|
8653
|
+
};
|
|
8654
|
+
};
|
|
8655
|
+
responses: {
|
|
8656
|
+
/** @description Per-recipient outcome; partial success is a normal result */
|
|
8657
|
+
200: {
|
|
8658
|
+
headers: {
|
|
8659
|
+
[name: string]: unknown;
|
|
8660
|
+
};
|
|
8661
|
+
content: {
|
|
8662
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8663
|
+
};
|
|
8664
|
+
};
|
|
8665
|
+
};
|
|
8666
|
+
};
|
|
8667
|
+
cancelInviteBatch: {
|
|
8668
|
+
parameters: {
|
|
8669
|
+
query?: never;
|
|
8670
|
+
header?: never;
|
|
8671
|
+
path: {
|
|
8672
|
+
id: string;
|
|
8673
|
+
};
|
|
8674
|
+
cookie?: never;
|
|
8675
|
+
};
|
|
8676
|
+
requestBody?: never;
|
|
8677
|
+
responses: {
|
|
8678
|
+
/** @description Cancelled; invitations already sent are unaffected */
|
|
8679
|
+
200: {
|
|
8680
|
+
headers: {
|
|
8681
|
+
[name: string]: unknown;
|
|
8682
|
+
};
|
|
8683
|
+
content: {
|
|
8684
|
+
"application/json": components["schemas"]["InviteBatchResponse"];
|
|
8685
|
+
};
|
|
8686
|
+
};
|
|
8687
|
+
};
|
|
8688
|
+
};
|
|
8277
8689
|
validateInviteToken: {
|
|
8278
8690
|
parameters: {
|
|
8279
8691
|
query: {
|
|
@@ -8296,6 +8708,26 @@ export interface operations {
|
|
|
8296
8708
|
};
|
|
8297
8709
|
};
|
|
8298
8710
|
};
|
|
8711
|
+
listOrgDirectory: {
|
|
8712
|
+
parameters: {
|
|
8713
|
+
query?: never;
|
|
8714
|
+
header?: never;
|
|
8715
|
+
path?: never;
|
|
8716
|
+
cookie?: never;
|
|
8717
|
+
};
|
|
8718
|
+
requestBody?: never;
|
|
8719
|
+
responses: {
|
|
8720
|
+
/** @description Every person in the org directory */
|
|
8721
|
+
200: {
|
|
8722
|
+
headers: {
|
|
8723
|
+
[name: string]: unknown;
|
|
8724
|
+
};
|
|
8725
|
+
content: {
|
|
8726
|
+
"application/json": components["schemas"]["OrgDirectoryResponse"];
|
|
8727
|
+
};
|
|
8728
|
+
};
|
|
8729
|
+
};
|
|
8730
|
+
};
|
|
8299
8731
|
listDomains: {
|
|
8300
8732
|
parameters: {
|
|
8301
8733
|
query?: never;
|
|
@@ -185,14 +185,22 @@ export const openApiRoutes = {
|
|
|
185
185
|
'/api/workspace/auth-policy/test-sso': ['POST'],
|
|
186
186
|
'/api/workspace/auth-policy/test-sso/{attemptId}': ['GET'],
|
|
187
187
|
'/api/workspace/auth-policy/validate-oidc': ['POST'],
|
|
188
|
+
'/api/workspace/directory': ['GET'],
|
|
188
189
|
'/api/workspace/domains': ['GET', 'POST'],
|
|
189
190
|
'/api/workspace/domains/{id}': ['DELETE'],
|
|
190
191
|
'/api/workspace/domains/{id}/verify': ['POST'],
|
|
191
192
|
'/api/workspace/handle': ['PATCH'],
|
|
193
|
+
'/api/workspace/invite-batches': ['POST'],
|
|
194
|
+
'/api/workspace/invite-batches/{id}': ['GET', 'PATCH'],
|
|
195
|
+
'/api/workspace/invite-batches/{id}/cancel': ['POST'],
|
|
196
|
+
'/api/workspace/invite-batches/{id}/prepare': ['POST'],
|
|
197
|
+
'/api/workspace/invite-batches/{id}/send': ['POST'],
|
|
192
198
|
'/api/workspace/invites': ['GET', 'POST'],
|
|
193
199
|
'/api/workspace/invites/accept': ['POST'],
|
|
200
|
+
'/api/workspace/invites/batch': ['POST'],
|
|
194
201
|
'/api/workspace/invites/validate': ['GET'],
|
|
195
202
|
'/api/workspace/invites/{id}': ['DELETE'],
|
|
203
|
+
'/api/workspace/invites/{id}/reissue': ['POST'],
|
|
196
204
|
'/api/workspace/members': ['GET'],
|
|
197
205
|
'/api/workspace/members/{id}': ['DELETE', 'GET'],
|
|
198
206
|
'/api/workspace/members/{id}/role': ['PATCH'],
|
package/src/index.ts
CHANGED
|
@@ -620,6 +620,64 @@ export type {
|
|
|
620
620
|
PositionReporting,
|
|
621
621
|
} from "./org/index";
|
|
622
622
|
|
|
623
|
+
// People directory: every `persons` row in the org, account-bearing or not.
|
|
624
|
+
// Neither the member list (RBAC principals) nor the org chart (topology) — the
|
|
625
|
+
// set an HRIS sync populates, and the only surface carrying a directory email.
|
|
626
|
+
// Capability-gated, deliberately: the org-chart read is authenticated-only.
|
|
627
|
+
export {
|
|
628
|
+
OrgDirectoryPersonSchema,
|
|
629
|
+
OrgDirectoryResponseSchema,
|
|
630
|
+
} from "./org/index";
|
|
631
|
+
export type { OrgDirectoryPerson, OrgDirectoryResponse } from "./org/index";
|
|
632
|
+
|
|
633
|
+
// Bulk invite, keyed by personId so recipients are resolved server-side from the
|
|
634
|
+
// caller's own directory rather than supplied by the caller.
|
|
635
|
+
export {
|
|
636
|
+
BATCH_INVITE_PERSONS_MAX,
|
|
637
|
+
BATCH_INVITE_SKIP_REASONS,
|
|
638
|
+
BatchInviteSkipReasonSchema,
|
|
639
|
+
BatchInviteRequestSchema,
|
|
640
|
+
BatchInviteResponseSchema,
|
|
641
|
+
} from "./org/index";
|
|
642
|
+
export type {
|
|
643
|
+
BatchInviteSkipReason,
|
|
644
|
+
BatchInviteRequest,
|
|
645
|
+
BatchInviteResponse,
|
|
646
|
+
} from "./org/index";
|
|
647
|
+
|
|
648
|
+
// Invite batches: the persisted Prepare → Review → Send lifecycle that puts a
|
|
649
|
+
// review boundary in front of bulk invite. The batch holds INTENT, never
|
|
650
|
+
// credentials — there is no token field on a batch or an item, and there never
|
|
651
|
+
// may be. Supersedes BatchInvite* above, which is removed in 58.0.0.
|
|
652
|
+
export {
|
|
653
|
+
INVITE_BATCH_STATUSES,
|
|
654
|
+
INVITE_BATCH_ITEM_STATUSES,
|
|
655
|
+
INVITE_BATCH_ITEM_REASONS,
|
|
656
|
+
INVITE_BATCH_CHANGED,
|
|
657
|
+
InviteBatchStatusSchema,
|
|
658
|
+
InviteBatchItemStatusSchema,
|
|
659
|
+
InviteBatchItemReasonSchema,
|
|
660
|
+
InviteBatchItemSchema,
|
|
661
|
+
InviteBatchCountsSchema,
|
|
662
|
+
InviteBatchSchema,
|
|
663
|
+
InviteBatchResponseSchema,
|
|
664
|
+
CreateInviteBatchRequestSchema,
|
|
665
|
+
UpdateInviteBatchRequestSchema,
|
|
666
|
+
SendInviteBatchRequestSchema,
|
|
667
|
+
} from "./org/index";
|
|
668
|
+
export type {
|
|
669
|
+
InviteBatchStatus,
|
|
670
|
+
InviteBatchItemStatus,
|
|
671
|
+
InviteBatchItemReason,
|
|
672
|
+
InviteBatchItem,
|
|
673
|
+
InviteBatchCounts,
|
|
674
|
+
InviteBatch,
|
|
675
|
+
InviteBatchResponse,
|
|
676
|
+
CreateInviteBatchRequest,
|
|
677
|
+
UpdateInviteBatchRequest,
|
|
678
|
+
SendInviteBatchRequest,
|
|
679
|
+
} from "./org/index";
|
|
680
|
+
|
|
623
681
|
// Source authority: per-field ownership — who may WRITE a given entity field.
|
|
624
682
|
// Orthogonal to provenance (who reported). (ADR-CONT-083 / ADR-CTRL-182)
|
|
625
683
|
export { SourceAuthoritySchema } from "./org/index";
|
package/src/org/README.md
CHANGED
|
@@ -37,6 +37,14 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
37
37
|
- `AuthoritySource` _(type)_
|
|
38
38
|
- `AuthoritySourceSchema` — Engine-internal authority source.
|
|
39
39
|
- `AuthorizableView` _(type)_ — Type for views that can be checked against VIEWSCOPEMAP.
|
|
40
|
+
- `BATCH_INVITE_PERSONS_MAX` — Cap on one batch.
|
|
41
|
+
- `BATCH_INVITE_SKIP_REASONS` — Why one person in a batch did not receive an invite. - `already-member` — holds an account in this org…
|
|
42
|
+
- `BatchInviteRequest` _(type)_
|
|
43
|
+
- `BatchInviteRequestSchema`
|
|
44
|
+
- `BatchInviteResponse` _(type)_
|
|
45
|
+
- `BatchInviteResponseSchema`
|
|
46
|
+
- `BatchInviteSkipReason` _(type)_
|
|
47
|
+
- `BatchInviteSkipReasonSchema`
|
|
40
48
|
- `COMPANY_MD_COLLAB_MAX_DOC_TEXT_BYTES` — Cap on the MATERIALIZED document text, in UTF-8 bytes of the expanded result.
|
|
41
49
|
- `COMPANY_MD_COLLAB_MAX_PRESENCE_POSITION_CHARS` — Cap on ONE presence position blob (`anchor` or `head`), in base64 chars.
|
|
42
50
|
- `COMPANY_MD_COLLAB_MAX_UPDATE_B64_CHARS` — Cap on ONE encoded update, measured on the base64 payload as it is sent.
|
|
@@ -101,6 +109,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
101
109
|
- `ConstraintViolationSchema` — One constraint the transformation engine evaluated and found violated.
|
|
102
110
|
- `CreateDelegationRequest` _(type)_
|
|
103
111
|
- `CreateDelegationRequestSchema`
|
|
112
|
+
- `CreateInviteBatchRequest` _(type)_
|
|
113
|
+
- `CreateInviteBatchRequestSchema`
|
|
104
114
|
- `CreateInviteRequest` _(type)_ — Request payload for creating an organization invite.
|
|
105
115
|
- `CreateInviteRequestPayload` _(type)_
|
|
106
116
|
- `CreateInviteRequestSchema`
|
|
@@ -139,9 +149,27 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
139
149
|
- `HomeUnitRole` _(type)_
|
|
140
150
|
- `HomeUnitRoleSchema` — Role the invitee takes in their home unit on acceptance.
|
|
141
151
|
- `IDENTITY_TRUST_LEVEL_LABELS`
|
|
152
|
+
- `INVITE_BATCH_CHANGED` — The error code a stale `send` (or a mutation of an already-claimed batch) returns, as `error` on the standard…
|
|
153
|
+
- `INVITE_BATCH_ITEM_REASONS` — Why an item is not `sent`.
|
|
154
|
+
- `INVITE_BATCH_ITEM_STATUSES` — Per-recipient outcome within a batch. - `eligible` — will be invited when the batch is sent. - `excluded` —…
|
|
155
|
+
- `INVITE_BATCH_STATUSES` — Batch lifecycle. - `draft` — created, membership not yet evaluated.
|
|
142
156
|
- `IdentityTrustLevel` _(type)_ — Identity Trust Level — org's identity posture
|
|
143
157
|
- `IntegrationRequest` _(type)_ — Advisory integration request created by a member.
|
|
144
158
|
- `IntegrationRequestStatus` _(type)_ — Status of an integration request from a member.
|
|
159
|
+
- `InviteBatch` _(type)_
|
|
160
|
+
- `InviteBatchCounts` _(type)_
|
|
161
|
+
- `InviteBatchCountsSchema` — Denormalized counters, maintained by the batch's own writers in the same transaction as the items they…
|
|
162
|
+
- `InviteBatchItem` _(type)_
|
|
163
|
+
- `InviteBatchItemReason` _(type)_
|
|
164
|
+
- `InviteBatchItemReasonSchema`
|
|
165
|
+
- `InviteBatchItemSchema`
|
|
166
|
+
- `InviteBatchItemStatus` _(type)_
|
|
167
|
+
- `InviteBatchItemStatusSchema`
|
|
168
|
+
- `InviteBatchResponse` _(type)_
|
|
169
|
+
- `InviteBatchResponseSchema`
|
|
170
|
+
- `InviteBatchSchema`
|
|
171
|
+
- `InviteBatchStatus` _(type)_
|
|
172
|
+
- `InviteBatchStatusSchema`
|
|
145
173
|
- `InviteListResponse` _(type)_
|
|
146
174
|
- `InviteListResponseSchema`
|
|
147
175
|
- `InviteResponse` _(type)_
|
|
@@ -178,6 +206,10 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
178
206
|
- `OrgBudgetConfigSchema`
|
|
179
207
|
- `OrgDeletionStatus` _(type)_
|
|
180
208
|
- `OrgDeletionStatusSchema`
|
|
209
|
+
- `OrgDirectoryPerson` _(type)_
|
|
210
|
+
- `OrgDirectoryPersonSchema`
|
|
211
|
+
- `OrgDirectoryResponse` _(type)_
|
|
212
|
+
- `OrgDirectoryResponseSchema`
|
|
181
213
|
- `OrgDivergence` _(type)_
|
|
182
214
|
- `OrgDivergenceResolution` _(type)_
|
|
183
215
|
- `OrgDivergenceResolutionSchema` — What resolving a divergence means today.
|
|
@@ -299,6 +331,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
299
331
|
- `ScopeCheckResponseSchema`
|
|
300
332
|
- `SelectionRationale` _(type)_
|
|
301
333
|
- `SelectionRationaleSchema`
|
|
334
|
+
- `SendInviteBatchRequest` _(type)_
|
|
335
|
+
- `SendInviteBatchRequestSchema` — `expectedRevision` is REQUIRED and has no default.
|
|
302
336
|
- `SetActiveOrgResponse` _(type)_
|
|
303
337
|
- `SetActiveOrgResponseSchema`
|
|
304
338
|
- `SetSeatManagerResponse` _(type)_
|
|
@@ -348,6 +382,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
348
382
|
- `UpdateAuthPolicyRequest` _(type)_ — Request payload for updating organization auth policy.
|
|
349
383
|
- `UpdateDelegationRequest` _(type)_
|
|
350
384
|
- `UpdateDelegationRequestSchema` — Change a delegation's expiry without revoking it: extend, shorten, or clear (set `expiresAt: null` to make it…
|
|
385
|
+
- `UpdateInviteBatchRequest` _(type)_
|
|
386
|
+
- `UpdateInviteBatchRequestSchema` — Change what a `draft` or `ready` batch will do.
|
|
351
387
|
- `UpdateOpenRoleStatusRequest` _(type)_
|
|
352
388
|
- `UpdateOpenRoleStatusRequestSchema` — Only the non-terminal client transitions are accepted; `fill` is its own endpoint and `open` is the seeded…
|
|
353
389
|
- `UpdateOrgUnitRequest` _(type)_
|
package/src/org/index.ts
CHANGED
|
@@ -354,6 +354,29 @@ export {
|
|
|
354
354
|
CreateInviteRequestSchema,
|
|
355
355
|
InviteResponseSchema,
|
|
356
356
|
InviteListResponseSchema,
|
|
357
|
+
// People directory + bulk invite (personId-keyed, capability-gated)
|
|
358
|
+
OrgDirectoryPersonSchema,
|
|
359
|
+
OrgDirectoryResponseSchema,
|
|
360
|
+
BATCH_INVITE_PERSONS_MAX,
|
|
361
|
+
BATCH_INVITE_SKIP_REASONS,
|
|
362
|
+
BatchInviteSkipReasonSchema,
|
|
363
|
+
BatchInviteRequestSchema,
|
|
364
|
+
BatchInviteResponseSchema,
|
|
365
|
+
// Invite batches — the persisted Prepare → Review → Send lifecycle
|
|
366
|
+
INVITE_BATCH_STATUSES,
|
|
367
|
+
INVITE_BATCH_ITEM_STATUSES,
|
|
368
|
+
INVITE_BATCH_ITEM_REASONS,
|
|
369
|
+
INVITE_BATCH_CHANGED,
|
|
370
|
+
InviteBatchStatusSchema,
|
|
371
|
+
InviteBatchItemStatusSchema,
|
|
372
|
+
InviteBatchItemReasonSchema,
|
|
373
|
+
InviteBatchItemSchema,
|
|
374
|
+
InviteBatchCountsSchema,
|
|
375
|
+
InviteBatchSchema,
|
|
376
|
+
InviteBatchResponseSchema,
|
|
377
|
+
CreateInviteBatchRequestSchema,
|
|
378
|
+
UpdateInviteBatchRequestSchema,
|
|
379
|
+
SendInviteBatchRequestSchema,
|
|
357
380
|
DomainResponseSchema,
|
|
358
381
|
DomainListResponseSchema,
|
|
359
382
|
OrgBillingResponseSchema,
|
|
@@ -366,6 +389,21 @@ export type {
|
|
|
366
389
|
CreateInviteRequestPayload,
|
|
367
390
|
InviteResponse,
|
|
368
391
|
InviteListResponse,
|
|
392
|
+
OrgDirectoryPerson,
|
|
393
|
+
OrgDirectoryResponse,
|
|
394
|
+
BatchInviteSkipReason,
|
|
395
|
+
BatchInviteRequest,
|
|
396
|
+
BatchInviteResponse,
|
|
397
|
+
InviteBatchStatus,
|
|
398
|
+
InviteBatchItemStatus,
|
|
399
|
+
InviteBatchItemReason,
|
|
400
|
+
InviteBatchItem,
|
|
401
|
+
InviteBatchCounts,
|
|
402
|
+
InviteBatch,
|
|
403
|
+
InviteBatchResponse,
|
|
404
|
+
CreateInviteBatchRequest,
|
|
405
|
+
UpdateInviteBatchRequest,
|
|
406
|
+
SendInviteBatchRequest,
|
|
369
407
|
DomainResponse,
|
|
370
408
|
DomainListResponse,
|
|
371
409
|
OrgBillingResponse,
|
package/src/org/schemas.ts
CHANGED
|
@@ -781,6 +781,434 @@ export type CreateInviteRequestPayload = z.infer<
|
|
|
781
781
|
typeof CreateInviteRequestSchema
|
|
782
782
|
>;
|
|
783
783
|
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
// GET /api/workspace/directory
|
|
786
|
+
//
|
|
787
|
+
// The org's PEOPLE DIRECTORY — every `persons` row, account-bearing or not.
|
|
788
|
+
// Distinct from the member list (`user_org_memberships`, RBAC principals) and
|
|
789
|
+
// from the org chart (`positions` + occupancy, topology): this is the answer to
|
|
790
|
+
// "who does this org know about", which after an HRIS sync is a much larger set
|
|
791
|
+
// than either.
|
|
792
|
+
//
|
|
793
|
+
// It exists because inviting a synced person needs their address, and the org
|
|
794
|
+
// chart cannot carry one: `GET /api/users/org-chart` is authenticated-only, so a
|
|
795
|
+
// `primaryEmail` there would let any member enumerate the whole company's email
|
|
796
|
+
// addresses. This surface is capability-gated instead.
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
|
|
799
|
+
export const OrgDirectoryPersonSchema = z.object({
|
|
800
|
+
personId: z.string().uuid(),
|
|
801
|
+
displayName: z.string(),
|
|
802
|
+
/**
|
|
803
|
+
* Nullable and NOT optional. A directory person with no address is a real,
|
|
804
|
+
* common state — an HRIS export with the email column unmapped produces a
|
|
805
|
+
* whole org of them — and it is the difference between "cannot be invited" and
|
|
806
|
+
* "not yet loaded". Callers must render the distinction rather than treat a
|
|
807
|
+
* missing address as absence of the person.
|
|
808
|
+
*/
|
|
809
|
+
primaryEmail: z.string().nullable(),
|
|
810
|
+
/** The linked account, or null when this person has never signed up. */
|
|
811
|
+
userId: z.string().uuid().nullable(),
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
export type OrgDirectoryPerson = z.infer<typeof OrgDirectoryPersonSchema>;
|
|
815
|
+
|
|
816
|
+
export const OrgDirectoryResponseSchema = z.object({
|
|
817
|
+
people: z.array(OrgDirectoryPersonSchema),
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
export type OrgDirectoryResponse = z.infer<typeof OrgDirectoryResponseSchema>;
|
|
821
|
+
|
|
822
|
+
// ---------------------------------------------------------------------------
|
|
823
|
+
// POST /api/workspace/invites/batch — DEPRECATED (removed in contracts 58.0.0)
|
|
824
|
+
//
|
|
825
|
+
// Superseded by the invite-batch lifecycle below. This route sends real email
|
|
826
|
+
// the instant it is called: there is no moment at which a human sees the
|
|
827
|
+
// addresses that are about to be written to, and no way to cancel. Everything it
|
|
828
|
+
// does correctly — personId keying, per-person transactions, three honest result
|
|
829
|
+
// buckets — is preserved by `POST /api/workspace/invite-batches/{id}/send`, with
|
|
830
|
+
// a review boundary in front of it.
|
|
831
|
+
//
|
|
832
|
+
// Kept live for one major so consumers can migrate against a green cascade: a
|
|
833
|
+
// single major that deleted these types while the app still imported them could
|
|
834
|
+
// not land, because the release cascade gates on each consumer's full CI tier.
|
|
835
|
+
// 57.0.0 adds the lifecycle; consumers move; 58.0.0 deletes everything below.
|
|
836
|
+
//
|
|
837
|
+
// Bulk invite, keyed by `personId` rather than by email. That is a security
|
|
838
|
+
// property, not a convenience: the single-invite route takes a caller-supplied
|
|
839
|
+
// address and can therefore reach any mailbox on the internet, which is why it
|
|
840
|
+
// carries a strict per-minute limit. This route's recipients are resolved
|
|
841
|
+
// server-side from the caller's own `persons` rows, so the reachable set is
|
|
842
|
+
// bounded by the org's own directory and the arbitrary-recipient risk is gone.
|
|
843
|
+
//
|
|
844
|
+
// Consequence for callers: the client never needs to hold an email address to
|
|
845
|
+
// send an invite, and an unknown or cross-org `personId` is silently omitted
|
|
846
|
+
// from the response rather than reported — reporting it would make this a
|
|
847
|
+
// membership oracle.
|
|
848
|
+
// ---------------------------------------------------------------------------
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Cap on one batch. Bounds worst-case request latency, since the invite emails
|
|
852
|
+
* are sent before the response returns (the raw token exists only in memory and
|
|
853
|
+
* must not be parked in a queue).
|
|
854
|
+
*/
|
|
855
|
+
export const BATCH_INVITE_PERSONS_MAX = 100;
|
|
856
|
+
|
|
857
|
+
/** @deprecated Use {@link CreateInviteBatchRequestSchema}. Removed in 58.0.0. */
|
|
858
|
+
export const BatchInviteRequestSchema = z.object({
|
|
859
|
+
personIds: z.array(z.string().uuid()).min(1).max(BATCH_INVITE_PERSONS_MAX),
|
|
860
|
+
// Same restricted {admin, member} domain as a single invite.
|
|
861
|
+
role: z.enum(["admin", "member"]),
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
export type BatchInviteRequest = z.infer<typeof BatchInviteRequestSchema>;
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Why one person in a batch did not receive an invite.
|
|
868
|
+
*
|
|
869
|
+
* - `already-member` — holds an account in this org already.
|
|
870
|
+
* - `invite-pending` — an unexpired invite is already outstanding.
|
|
871
|
+
* - `no-email` — the directory row carries no address to send to.
|
|
872
|
+
* - `send-failed` — the invite row COMMITTED but the email did not leave.
|
|
873
|
+
* Recoverable by resending; never a reason to roll the
|
|
874
|
+
* invite back.
|
|
875
|
+
*/
|
|
876
|
+
export const BATCH_INVITE_SKIP_REASONS = [
|
|
877
|
+
"already-member",
|
|
878
|
+
"invite-pending",
|
|
879
|
+
"no-email",
|
|
880
|
+
"send-failed",
|
|
881
|
+
] as const;
|
|
882
|
+
|
|
883
|
+
/** @deprecated Use {@link InviteBatchItemReasonSchema}. Removed in 58.0.0. */
|
|
884
|
+
export const BatchInviteSkipReasonSchema = z.enum(BATCH_INVITE_SKIP_REASONS);
|
|
885
|
+
|
|
886
|
+
export type BatchInviteSkipReason = z.infer<typeof BatchInviteSkipReasonSchema>;
|
|
887
|
+
|
|
888
|
+
const BatchInviteOutcomeSchema = z.object({
|
|
889
|
+
personId: z.string().uuid(),
|
|
890
|
+
reason: BatchInviteSkipReasonSchema,
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
/** @deprecated Use {@link InviteBatchResponseSchema}. Removed in 58.0.0. */
|
|
894
|
+
export const BatchInviteResponseSchema = z.object({
|
|
895
|
+
/** Person ids that now hold a pending invite AND were emailed. */
|
|
896
|
+
invited: z.array(z.string().uuid()),
|
|
897
|
+
/** Never attempted — see {@link BatchInviteSkipReason}. */
|
|
898
|
+
skipped: z.array(BatchInviteOutcomeSchema),
|
|
899
|
+
/**
|
|
900
|
+
* Invite row exists, delivery did not. Separate from `skipped` because the
|
|
901
|
+
* remedy differs: a skip needs the underlying state to change, a failure needs
|
|
902
|
+
* a resend.
|
|
903
|
+
*/
|
|
904
|
+
failed: z.array(BatchInviteOutcomeSchema),
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
export type BatchInviteResponse = z.infer<typeof BatchInviteResponseSchema>;
|
|
908
|
+
|
|
909
|
+
// ---------------------------------------------------------------------------
|
|
910
|
+
// Invite batches — the Prepare → Review → Send lifecycle
|
|
911
|
+
//
|
|
912
|
+
// POST /api/workspace/invite-batches create a draft
|
|
913
|
+
// GET /api/workspace/invite-batches/{id} review it
|
|
914
|
+
// PATCH /api/workspace/invite-batches/{id} change selection/role (bumps revision)
|
|
915
|
+
// POST /api/workspace/invite-batches/{id}/prepare evaluate eligibility, draft → ready
|
|
916
|
+
// POST /api/workspace/invite-batches/{id}/send send, gated on expectedRevision
|
|
917
|
+
// POST /api/workspace/invite-batches/{id}/cancel cancel what has not been sent
|
|
918
|
+
//
|
|
919
|
+
// WHY A PERSISTED BATCH AT ALL. The one-shot bulk route (`POST
|
|
920
|
+
// /api/workspace/invites/batch`, deprecated by this vocabulary) sends real email
|
|
921
|
+
// the instant it is called — there is no moment at which a human can see the 88
|
|
922
|
+
// addresses that are about to be written to. This lifecycle inserts exactly one:
|
|
923
|
+
// `prepare` decides who is eligible, the review surface shows the recipients and
|
|
924
|
+
// their addresses, and `send` acts on what was reviewed or refuses.
|
|
925
|
+
//
|
|
926
|
+
// THE BATCH HOLDS INTENT, NEVER CREDENTIALS. There is no token field on a batch
|
|
927
|
+
// or an item, in any form, and there never may be. An invite token exists only
|
|
928
|
+
// between generation and the send, in memory; only its SHA-256 is stored, on the
|
|
929
|
+
// invite row. A batch that parked a live token at rest would undo that whole
|
|
930
|
+
// property in exchange for nothing — the item carries `inviteId`, which is the
|
|
931
|
+
// durable handle to the credential without being one.
|
|
932
|
+
//
|
|
933
|
+
// CONFIRMATION AUTHORIZES THE ADDRESSES SHOWN AT REVIEW TIME. `emailSnapshot` is
|
|
934
|
+
// what the reviewer saw. At send, an item whose person now holds a different
|
|
935
|
+
// address is `skipped` with `address-changed` — never mailed at the new one. The
|
|
936
|
+
// same is true of someone who became a member, or gained an invite by another
|
|
937
|
+
// route, between review and send. Approving a list is not approving whatever
|
|
938
|
+
// that list becomes.
|
|
939
|
+
// ---------------------------------------------------------------------------
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Batch lifecycle.
|
|
943
|
+
*
|
|
944
|
+
* - `draft` — created, membership not yet evaluated. Sends nothing.
|
|
945
|
+
* - `ready` — `prepare` ran; the item set and its exclusions are the review.
|
|
946
|
+
* - `sending` — CLAIMED by a send. See {@link SendInviteBatchRequestSchema}.
|
|
947
|
+
* - `completed` — the send finished. Per-person outcomes live on the items.
|
|
948
|
+
* - `cancelled` — cancelled before or between sends; unsent items stay unsent.
|
|
949
|
+
*
|
|
950
|
+
* There is deliberately no `partially_completed`: it is `failedCount > 0` on a
|
|
951
|
+
* `completed` batch, and a second spelling of one fact is a second thing to keep
|
|
952
|
+
* true. There is deliberately no `expired` either — nothing sweeps batches, and
|
|
953
|
+
* a status no writer ever writes is a promise the API does not keep.
|
|
954
|
+
*/
|
|
955
|
+
export const INVITE_BATCH_STATUSES = [
|
|
956
|
+
"draft",
|
|
957
|
+
"ready",
|
|
958
|
+
"sending",
|
|
959
|
+
"completed",
|
|
960
|
+
"cancelled",
|
|
961
|
+
] as const;
|
|
962
|
+
|
|
963
|
+
export const InviteBatchStatusSchema = z.enum(INVITE_BATCH_STATUSES);
|
|
964
|
+
|
|
965
|
+
export type InviteBatchStatus = z.infer<typeof InviteBatchStatusSchema>;
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Per-recipient outcome within a batch.
|
|
969
|
+
*
|
|
970
|
+
* - `eligible` — will be invited when the batch is sent.
|
|
971
|
+
* - `excluded` — will not, decided at PREPARE time. See {@link InviteBatchItemReason}.
|
|
972
|
+
* - `sent` — an invite row exists AND the email left. Carries `inviteId`.
|
|
973
|
+
* - `failed` — an invite row exists, the email did NOT leave. Also carries
|
|
974
|
+
* `inviteId`: the remedy is a reissue, never a re-send, because
|
|
975
|
+
* the credential in that row was already minted.
|
|
976
|
+
* - `skipped` — eligible at review, refused at SEND because the world moved.
|
|
977
|
+
*
|
|
978
|
+
* `eligible` items on a batch stuck in `sending` are exactly the ones a crashed
|
|
979
|
+
* send never reached — which is why there is no separate `pending_send`.
|
|
980
|
+
*/
|
|
981
|
+
export const INVITE_BATCH_ITEM_STATUSES = [
|
|
982
|
+
"eligible",
|
|
983
|
+
"excluded",
|
|
984
|
+
"sent",
|
|
985
|
+
"failed",
|
|
986
|
+
"skipped",
|
|
987
|
+
] as const;
|
|
988
|
+
|
|
989
|
+
export const InviteBatchItemStatusSchema = z.enum(INVITE_BATCH_ITEM_STATUSES);
|
|
990
|
+
|
|
991
|
+
export type InviteBatchItemStatus = z.infer<typeof InviteBatchItemStatusSchema>;
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Why an item is not `sent`. One vocabulary across `excluded` (decided at
|
|
995
|
+
* prepare) and `skipped` (decided at send) — the reader wants "why did this
|
|
996
|
+
* person not get an invitation", and splitting the answer by which stage
|
|
997
|
+
* noticed would make that two questions.
|
|
998
|
+
*
|
|
999
|
+
* - `already-member` — holds an account in this org.
|
|
1000
|
+
* - `invite-pending` — an unexpired invite is already outstanding.
|
|
1001
|
+
* - `no-email` — the directory row carries no address.
|
|
1002
|
+
* - `invalid-email` — it carries one that is not a deliverable address.
|
|
1003
|
+
* An HRIS export with a mangled column produces these;
|
|
1004
|
+
* committing an invite row that can never be mailed is
|
|
1005
|
+
* the failure this reason exists to prevent.
|
|
1006
|
+
* - `removed-by-reviewer` — a human un-ticked them. Persisted rather than
|
|
1007
|
+
* deleted so the choice is reversible AND legible:
|
|
1008
|
+
* "who was deliberately left out" is a question the
|
|
1009
|
+
* batch must be able to answer later.
|
|
1010
|
+
* - `address-changed` — SEND-time only. The person's address is no longer
|
|
1011
|
+
* the one shown at review. Confirmation authorized the
|
|
1012
|
+
* address, not the person.
|
|
1013
|
+
* - `person-removed` — SEND-time only. The directory row is gone.
|
|
1014
|
+
* - `send-failed` — pairs with `failed`, not with `excluded`/`skipped`.
|
|
1015
|
+
* - `inactive-in-hris` — RESERVED, and NOTHING WRITES IT TODAY. `persons`
|
|
1016
|
+
* carries no employment-status column, the BambooHR
|
|
1017
|
+
* client never reads one, and a "Sync now" full resync
|
|
1018
|
+
* reports no departures at all — so a terminated
|
|
1019
|
+
* employee is currently indistinguishable from a
|
|
1020
|
+
* current one. The name is held here so the eventual
|
|
1021
|
+
* implementation does not have to change the wire
|
|
1022
|
+
* vocabulary; treating its ABSENCE as evidence that
|
|
1023
|
+
* everyone listed is still employed would be wrong.
|
|
1024
|
+
*/
|
|
1025
|
+
export const INVITE_BATCH_ITEM_REASONS = [
|
|
1026
|
+
"already-member",
|
|
1027
|
+
"invite-pending",
|
|
1028
|
+
"no-email",
|
|
1029
|
+
"invalid-email",
|
|
1030
|
+
"removed-by-reviewer",
|
|
1031
|
+
"address-changed",
|
|
1032
|
+
"person-removed",
|
|
1033
|
+
"send-failed",
|
|
1034
|
+
"inactive-in-hris",
|
|
1035
|
+
] as const;
|
|
1036
|
+
|
|
1037
|
+
export const InviteBatchItemReasonSchema = z.enum(INVITE_BATCH_ITEM_REASONS);
|
|
1038
|
+
|
|
1039
|
+
export type InviteBatchItemReason = z.infer<typeof InviteBatchItemReasonSchema>;
|
|
1040
|
+
|
|
1041
|
+
export const InviteBatchItemSchema = z.object({
|
|
1042
|
+
personId: z.string().uuid(),
|
|
1043
|
+
/**
|
|
1044
|
+
* Read live from the directory, NOT snapshotted. A name is for recognising the
|
|
1045
|
+
* human; only the ADDRESS is the thing confirmation authorizes, so only the
|
|
1046
|
+
* address is frozen. Null when the directory row has since been deleted.
|
|
1047
|
+
*/
|
|
1048
|
+
displayName: z.string().nullable(),
|
|
1049
|
+
/**
|
|
1050
|
+
* The address as it stood when `prepare` ran — what the reviewer approved.
|
|
1051
|
+
* Null only when the person had none. Compared against the live address at
|
|
1052
|
+
* send; a mismatch is `address-changed`, never a send to the new one.
|
|
1053
|
+
*/
|
|
1054
|
+
emailSnapshot: z.string().nullable(),
|
|
1055
|
+
role: z.enum(["admin", "member"]),
|
|
1056
|
+
status: InviteBatchItemStatusSchema,
|
|
1057
|
+
/** Null exactly when `status` is `eligible` or `sent`. */
|
|
1058
|
+
reason: InviteBatchItemReasonSchema.nullable(),
|
|
1059
|
+
/**
|
|
1060
|
+
* The invite row this item produced. Present for `sent` AND for `failed` — a
|
|
1061
|
+
* failed item's credential was minted and committed before the send was
|
|
1062
|
+
* attempted, so the row exists and reissue is the remedy.
|
|
1063
|
+
*/
|
|
1064
|
+
inviteId: z.string().uuid().nullable(),
|
|
1065
|
+
/**
|
|
1066
|
+
* The snapshot address is not under a domain this org has VERIFIED. Computed
|
|
1067
|
+
* at read time from `org_domains` (status `verified`), never stored: a domain
|
|
1068
|
+
* verified after review must stop warning, and one revoked after review must
|
|
1069
|
+
* start. Advisory — it does not exclude anyone. Sending outside your own
|
|
1070
|
+
* domains is legitimate (contractors, personal addresses); doing it eighty-
|
|
1071
|
+
* eight times without noticing is the accident worth surfacing.
|
|
1072
|
+
*/
|
|
1073
|
+
emailOutsideVerifiedDomain: z.boolean(),
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
export type InviteBatchItem = z.infer<typeof InviteBatchItemSchema>;
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Denormalized counters, maintained by the batch's own writers in the same
|
|
1080
|
+
* transaction as the items they describe.
|
|
1081
|
+
*
|
|
1082
|
+
* DELIBERATELY NOT DERIVED CLIENT-SIDE. The confirm control reads
|
|
1083
|
+
* "Send {eligible} invitations", and that number must be the number the SERVER
|
|
1084
|
+
* will act on. A count computed from a page of items can disagree with the set
|
|
1085
|
+
* `send` iterates; a count the server wrote cannot.
|
|
1086
|
+
*/
|
|
1087
|
+
export const InviteBatchCountsSchema = z.object({
|
|
1088
|
+
/** Every person the batch was created for. Never changes after create. */
|
|
1089
|
+
candidate: z.number().int().nonnegative(),
|
|
1090
|
+
eligible: z.number().int().nonnegative(),
|
|
1091
|
+
excluded: z.number().int().nonnegative(),
|
|
1092
|
+
sent: z.number().int().nonnegative(),
|
|
1093
|
+
failed: z.number().int().nonnegative(),
|
|
1094
|
+
skipped: z.number().int().nonnegative(),
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
export type InviteBatchCounts = z.infer<typeof InviteBatchCountsSchema>;
|
|
1098
|
+
|
|
1099
|
+
export const InviteBatchSchema = z.object({
|
|
1100
|
+
id: z.string().uuid(),
|
|
1101
|
+
status: InviteBatchStatusSchema,
|
|
1102
|
+
/**
|
|
1103
|
+
* Where the candidate set came from. OPEN string, `directory` today — the org
|
|
1104
|
+
* people directory. Not an enum: a future CSV or SCIM origin should not need a
|
|
1105
|
+
* migration to be nameable.
|
|
1106
|
+
*/
|
|
1107
|
+
source: z.string(),
|
|
1108
|
+
/**
|
|
1109
|
+
* Bumped by every mutation of the reviewable set (selection or role). `send`
|
|
1110
|
+
* carries the revision the reviewer saw; a mismatch is
|
|
1111
|
+
* {@link INVITE_BATCH_CHANGED} rather than a send of something else.
|
|
1112
|
+
*/
|
|
1113
|
+
revision: z.number().int().nonnegative(),
|
|
1114
|
+
counts: InviteBatchCountsSchema,
|
|
1115
|
+
createdByUserId: z.string().uuid(),
|
|
1116
|
+
/** Who ran `send`, and when they did. Null until then. */
|
|
1117
|
+
confirmedByUserId: z.string().uuid().nullable(),
|
|
1118
|
+
confirmedAt: z.string().nullable(),
|
|
1119
|
+
createdAt: z.string(),
|
|
1120
|
+
updatedAt: z.string(),
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
export type InviteBatch = z.infer<typeof InviteBatchSchema>;
|
|
1124
|
+
|
|
1125
|
+
export const InviteBatchResponseSchema = z.object({
|
|
1126
|
+
batch: InviteBatchSchema,
|
|
1127
|
+
items: z.array(InviteBatchItemSchema),
|
|
1128
|
+
/**
|
|
1129
|
+
* The org's verified domains, so the review surface can NAME them in the
|
|
1130
|
+
* out-of-domain warning rather than just flagging rows. Empty is the common
|
|
1131
|
+
* case and means every address is out-of-domain by this definition — render
|
|
1132
|
+
* the warning accordingly rather than as eighty-eight alarms.
|
|
1133
|
+
*/
|
|
1134
|
+
verifiedDomains: z.array(z.string()),
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
export type InviteBatchResponse = z.infer<typeof InviteBatchResponseSchema>;
|
|
1138
|
+
|
|
1139
|
+
export const CreateInviteBatchRequestSchema = z.object({
|
|
1140
|
+
personIds: z.array(z.string().uuid()).min(1).max(BATCH_INVITE_PERSONS_MAX),
|
|
1141
|
+
role: z.enum(["admin", "member"]),
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
export type CreateInviteBatchRequest = z.infer<
|
|
1145
|
+
typeof CreateInviteBatchRequestSchema
|
|
1146
|
+
>;
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Change what a `draft` or `ready` batch will do. Every accepted call bumps
|
|
1150
|
+
* `revision`, which is what invalidates a review already on someone's screen.
|
|
1151
|
+
*
|
|
1152
|
+
* Deselecting does NOT delete the item — it becomes `excluded` with
|
|
1153
|
+
* `removed-by-reviewer`, so reselecting is a status flip rather than a
|
|
1154
|
+
* re-preparation, and the batch can still answer who was deliberately omitted.
|
|
1155
|
+
* Reselecting a person excluded by POLICY (already a member, no address) is
|
|
1156
|
+
* refused: `reselect` reverses a human's decision, not the server's.
|
|
1157
|
+
*/
|
|
1158
|
+
export const UpdateInviteBatchRequestSchema = z
|
|
1159
|
+
.object({
|
|
1160
|
+
deselectPersonIds: z.array(z.string().uuid()).optional(),
|
|
1161
|
+
reselectPersonIds: z.array(z.string().uuid()).optional(),
|
|
1162
|
+
role: z.enum(["admin", "member"]).optional(),
|
|
1163
|
+
})
|
|
1164
|
+
.refine(
|
|
1165
|
+
(v) =>
|
|
1166
|
+
v.deselectPersonIds !== undefined ||
|
|
1167
|
+
v.reselectPersonIds !== undefined ||
|
|
1168
|
+
v.role !== undefined,
|
|
1169
|
+
{
|
|
1170
|
+
message:
|
|
1171
|
+
"Provide at least one of deselectPersonIds, reselectPersonIds, role",
|
|
1172
|
+
},
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
export type UpdateInviteBatchRequest = z.infer<
|
|
1176
|
+
typeof UpdateInviteBatchRequestSchema
|
|
1177
|
+
>;
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* `expectedRevision` is REQUIRED and has no default. Sending is the only
|
|
1181
|
+
* irreversible verb in this lifecycle — email cannot be recalled — so the one
|
|
1182
|
+
* call that performs it must state what it believes it is sending.
|
|
1183
|
+
*
|
|
1184
|
+
* There is no `Idempotency-Key` here, deliberately. The send claim
|
|
1185
|
+
* (`UPDATE ... SET status='sending' WHERE status='ready' AND revision = ?`) is
|
|
1186
|
+
* an atomic compare-and-set: the first caller wins and every later one sees zero
|
|
1187
|
+
* rows and gets {@link INVITE_BATCH_CHANGED}. That is exactly-once permanently
|
|
1188
|
+
* and across every task in the cluster, where an idempotency key would be
|
|
1189
|
+
* exactly-once for its retention window and only where the key store is shared.
|
|
1190
|
+
* Routing this through a key ALSO means wrapping check + mutation + store in one
|
|
1191
|
+
* transaction, which would roll back the record of mail that had already left.
|
|
1192
|
+
*/
|
|
1193
|
+
export const SendInviteBatchRequestSchema = z.object({
|
|
1194
|
+
expectedRevision: z.number().int().nonnegative(),
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
export type SendInviteBatchRequest = z.infer<
|
|
1198
|
+
typeof SendInviteBatchRequestSchema
|
|
1199
|
+
>;
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* The error code a stale `send` (or a mutation of an already-claimed batch)
|
|
1203
|
+
* returns, as `error` on the standard 409 envelope.
|
|
1204
|
+
*
|
|
1205
|
+
* It means: what you reviewed is not what is here now. The client's obligation
|
|
1206
|
+
* is to re-fetch and put the CHANGES in front of the human again before the
|
|
1207
|
+
* confirm control re-arms — not to retry with the new revision, which would
|
|
1208
|
+
* authorize a list nobody read.
|
|
1209
|
+
*/
|
|
1210
|
+
export const INVITE_BATCH_CHANGED = "BATCH_CHANGED" as const;
|
|
1211
|
+
|
|
784
1212
|
// ---------------------------------------------------------------------------
|
|
785
1213
|
// OrgDomain sub-schema
|
|
786
1214
|
// ---------------------------------------------------------------------------
|
|
@@ -20,6 +20,9 @@ export type ResourceKey =
|
|
|
20
20
|
| { type: "teams"; orgId: string }
|
|
21
21
|
| { type: "integrations"; orgId: string }
|
|
22
22
|
| { type: "invites"; orgId: string }
|
|
23
|
+
// The people directory (`persons`) — every person the org knows about,
|
|
24
|
+
// account-bearing or not. Distinct from `members`, which is accounts only.
|
|
25
|
+
| { type: "orgDirectory"; orgId: string }
|
|
23
26
|
| { type: "auditEvents"; orgId: string }
|
|
24
27
|
| { type: "timeline"; orgId: string }
|
|
25
28
|
// Identities (singular) — single entities
|
package/src/resource-keys.ts
CHANGED
|
@@ -129,6 +129,7 @@ const ORG_SCOPED_TYPES = [
|
|
|
129
129
|
"teams",
|
|
130
130
|
"integrations",
|
|
131
131
|
"invites",
|
|
132
|
+
"orgDirectory",
|
|
132
133
|
"auditEvents",
|
|
133
134
|
"timeline",
|
|
134
135
|
"workspace",
|
|
@@ -266,6 +267,7 @@ export function toQueryKey(key: ResourceKey): readonly string[] {
|
|
|
266
267
|
case "teams":
|
|
267
268
|
case "integrations":
|
|
268
269
|
case "invites":
|
|
270
|
+
case "orgDirectory":
|
|
269
271
|
case "auditEvents":
|
|
270
272
|
case "timeline":
|
|
271
273
|
case "workspace":
|