@company-semantics/contracts 56.1.0 → 58.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 +325 -0
- package/src/generated/openapi-routes.ts +6 -0
- package/src/index.ts +33 -0
- package/src/integrations/README.md +4 -0
- package/src/integrations/schemas.ts +26 -0
- package/src/org/README.md +24 -0
- package/src/org/index.ts +25 -0
- package/src/org/schemas.ts +319 -1
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
|
@@ -943,6 +943,23 @@ export interface paths {
|
|
|
943
943
|
patch?: never;
|
|
944
944
|
trace?: never;
|
|
945
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
|
+
};
|
|
946
963
|
"/api/workspace/invites/accept": {
|
|
947
964
|
parameters: {
|
|
948
965
|
query?: never;
|
|
@@ -977,6 +994,92 @@ export interface paths {
|
|
|
977
994
|
patch?: never;
|
|
978
995
|
trace?: never;
|
|
979
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
|
+
};
|
|
980
1083
|
"/api/workspace/invites/validate": {
|
|
981
1084
|
parameters: {
|
|
982
1085
|
query?: never;
|
|
@@ -4631,6 +4734,12 @@ export interface components {
|
|
|
4631
4734
|
/** @enum {string} */
|
|
4632
4735
|
role: "admin" | "member";
|
|
4633
4736
|
};
|
|
4737
|
+
ReissueInviteResponse: {
|
|
4738
|
+
/** Format: uuid */
|
|
4739
|
+
inviteId: string;
|
|
4740
|
+
/** Format: uuid */
|
|
4741
|
+
previousInviteId: string;
|
|
4742
|
+
};
|
|
4634
4743
|
InviteListResponse: {
|
|
4635
4744
|
id: string;
|
|
4636
4745
|
orgId: string;
|
|
@@ -4674,6 +4783,58 @@ export interface components {
|
|
|
4674
4783
|
AcceptInviteRequest: {
|
|
4675
4784
|
token: string;
|
|
4676
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
|
+
};
|
|
4677
4838
|
InviteValidationResult: {
|
|
4678
4839
|
valid: boolean;
|
|
4679
4840
|
status: string;
|
|
@@ -8317,6 +8478,28 @@ export interface operations {
|
|
|
8317
8478
|
};
|
|
8318
8479
|
};
|
|
8319
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
|
+
};
|
|
8320
8503
|
acceptInvite: {
|
|
8321
8504
|
parameters: {
|
|
8322
8505
|
query?: never;
|
|
@@ -8361,6 +8544,148 @@ export interface operations {
|
|
|
8361
8544
|
};
|
|
8362
8545
|
};
|
|
8363
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
|
+
};
|
|
8364
8689
|
validateInviteToken: {
|
|
8365
8690
|
parameters: {
|
|
8366
8691
|
query: {
|
|
@@ -190,11 +190,17 @@ export const openApiRoutes = {
|
|
|
190
190
|
'/api/workspace/domains/{id}': ['DELETE'],
|
|
191
191
|
'/api/workspace/domains/{id}/verify': ['POST'],
|
|
192
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'],
|
|
193
198
|
'/api/workspace/invites': ['GET', 'POST'],
|
|
194
199
|
'/api/workspace/invites/accept': ['POST'],
|
|
195
200
|
'/api/workspace/invites/batch': ['POST'],
|
|
196
201
|
'/api/workspace/invites/validate': ['GET'],
|
|
197
202
|
'/api/workspace/invites/{id}': ['DELETE'],
|
|
203
|
+
'/api/workspace/invites/{id}/reissue': ['POST'],
|
|
198
204
|
'/api/workspace/members': ['GET'],
|
|
199
205
|
'/api/workspace/members/{id}': ['DELETE', 'GET'],
|
|
200
206
|
'/api/workspace/members/{id}/role': ['PATCH'],
|
package/src/index.ts
CHANGED
|
@@ -645,6 +645,39 @@ export type {
|
|
|
645
645
|
BatchInviteResponse,
|
|
646
646
|
} from "./org/index";
|
|
647
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
|
+
|
|
648
681
|
// Source authority: per-field ownership — who may WRITE a given entity field.
|
|
649
682
|
// Orthogonal to provenance (who reported). (ADR-CONT-083 / ADR-CTRL-182)
|
|
650
683
|
export { SourceAuthoritySchema } from "./org/index";
|
|
@@ -16,6 +16,10 @@ alongside `Meetings`, `Comms`, and `Docs`.
|
|
|
16
16
|
|
|
17
17
|
- `HrConnectionStatusSchema` is a read projection: count and sync fields are
|
|
18
18
|
`null` until the first successful sync (a connected, never-synced integration).
|
|
19
|
+
- `revokedReason` is non-null only when `credentialState` is `'unusable'`, and
|
|
20
|
+
its enum mirrors the backend's `ConnectionRevokedReason` exactly — the app
|
|
21
|
+
`parse`s this projection, so a value missing from the enum throws rather than
|
|
22
|
+
degrades (ADR-CONTRACTS-138).
|
|
19
23
|
- `HrConnectInputSchema` carries only the customer's provider `subdomain`; no
|
|
20
24
|
secrets or provider credentials live in contracts.
|
|
21
25
|
- Pure vocabulary — `zod` is the only runtime import (vocabulary-guard).
|
|
@@ -83,6 +83,32 @@ export const HrConnectionStatusSchema = z
|
|
|
83
83
|
description:
|
|
84
84
|
"Health of the most recent sync; null when no sync has run yet.",
|
|
85
85
|
}),
|
|
86
|
+
revokedReason: z
|
|
87
|
+
.enum([
|
|
88
|
+
"token_refresh_failed",
|
|
89
|
+
"missing_refresh_token",
|
|
90
|
+
"member_removed",
|
|
91
|
+
"app_uninstalled",
|
|
92
|
+
"tokens_revoked",
|
|
93
|
+
])
|
|
94
|
+
.nullable()
|
|
95
|
+
.meta({
|
|
96
|
+
description:
|
|
97
|
+
"Why the credential died, when `credentialState` is 'unusable'. " +
|
|
98
|
+
"'token_refresh_failed' — the provider rejected the refresh grant, " +
|
|
99
|
+
"which is a sign-in that simply aged out and is fixed by " +
|
|
100
|
+
"reconnecting. 'missing_refresh_token' — there is nothing left to " +
|
|
101
|
+
"refresh with, so reconnecting is the ONLY repair. " +
|
|
102
|
+
"'member_removed' — the person whose account backed this " +
|
|
103
|
+
"connection left the org, so someone else must reconnect it. " +
|
|
104
|
+
"'app_uninstalled' / 'tokens_revoked' — the grant was withdrawn on " +
|
|
105
|
+
"the provider's side, and reconnecting will fail again until it is " +
|
|
106
|
+
"restored there. The distinction matters because the four cases " +
|
|
107
|
+
"ask different things of the reader: three are 'click Reconnect' " +
|
|
108
|
+
"and one is 'go fix it in the provider first'. Null whenever " +
|
|
109
|
+
"`credentialState` is not 'unusable', and null on a connection " +
|
|
110
|
+
"revoked before this field existed.",
|
|
111
|
+
}),
|
|
86
112
|
})
|
|
87
113
|
.meta({
|
|
88
114
|
description: "Connection-status projection for an HR integration.",
|
package/src/org/README.md
CHANGED
|
@@ -109,6 +109,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
109
109
|
- `ConstraintViolationSchema` — One constraint the transformation engine evaluated and found violated.
|
|
110
110
|
- `CreateDelegationRequest` _(type)_
|
|
111
111
|
- `CreateDelegationRequestSchema`
|
|
112
|
+
- `CreateInviteBatchRequest` _(type)_
|
|
113
|
+
- `CreateInviteBatchRequestSchema`
|
|
112
114
|
- `CreateInviteRequest` _(type)_ — Request payload for creating an organization invite.
|
|
113
115
|
- `CreateInviteRequestPayload` _(type)_
|
|
114
116
|
- `CreateInviteRequestSchema`
|
|
@@ -147,9 +149,27 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
147
149
|
- `HomeUnitRole` _(type)_
|
|
148
150
|
- `HomeUnitRoleSchema` — Role the invitee takes in their home unit on acceptance.
|
|
149
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.
|
|
150
156
|
- `IdentityTrustLevel` _(type)_ — Identity Trust Level — org's identity posture
|
|
151
157
|
- `IntegrationRequest` _(type)_ — Advisory integration request created by a member.
|
|
152
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`
|
|
153
173
|
- `InviteListResponse` _(type)_
|
|
154
174
|
- `InviteListResponseSchema`
|
|
155
175
|
- `InviteResponse` _(type)_
|
|
@@ -311,6 +331,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
311
331
|
- `ScopeCheckResponseSchema`
|
|
312
332
|
- `SelectionRationale` _(type)_
|
|
313
333
|
- `SelectionRationaleSchema`
|
|
334
|
+
- `SendInviteBatchRequest` _(type)_
|
|
335
|
+
- `SendInviteBatchRequestSchema` — `expectedRevision` is REQUIRED and has no default.
|
|
314
336
|
- `SetActiveOrgResponse` _(type)_
|
|
315
337
|
- `SetActiveOrgResponseSchema`
|
|
316
338
|
- `SetSeatManagerResponse` _(type)_
|
|
@@ -360,6 +382,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
360
382
|
- `UpdateAuthPolicyRequest` _(type)_ — Request payload for updating organization auth policy.
|
|
361
383
|
- `UpdateDelegationRequest` _(type)_
|
|
362
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.
|
|
363
387
|
- `UpdateOpenRoleStatusRequest` _(type)_
|
|
364
388
|
- `UpdateOpenRoleStatusRequestSchema` — Only the non-terminal client transitions are accepted; `fill` is its own endpoint and `open` is the seeded…
|
|
365
389
|
- `UpdateOrgUnitRequest` _(type)_
|
package/src/org/index.ts
CHANGED
|
@@ -362,6 +362,21 @@ export {
|
|
|
362
362
|
BatchInviteSkipReasonSchema,
|
|
363
363
|
BatchInviteRequestSchema,
|
|
364
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,
|
|
365
380
|
DomainResponseSchema,
|
|
366
381
|
DomainListResponseSchema,
|
|
367
382
|
OrgBillingResponseSchema,
|
|
@@ -379,6 +394,16 @@ export type {
|
|
|
379
394
|
BatchInviteSkipReason,
|
|
380
395
|
BatchInviteRequest,
|
|
381
396
|
BatchInviteResponse,
|
|
397
|
+
InviteBatchStatus,
|
|
398
|
+
InviteBatchItemStatus,
|
|
399
|
+
InviteBatchItemReason,
|
|
400
|
+
InviteBatchItem,
|
|
401
|
+
InviteBatchCounts,
|
|
402
|
+
InviteBatch,
|
|
403
|
+
InviteBatchResponse,
|
|
404
|
+
CreateInviteBatchRequest,
|
|
405
|
+
UpdateInviteBatchRequest,
|
|
406
|
+
SendInviteBatchRequest,
|
|
382
407
|
DomainResponse,
|
|
383
408
|
DomainListResponse,
|
|
384
409
|
OrgBillingResponse,
|
package/src/org/schemas.ts
CHANGED
|
@@ -820,7 +820,19 @@ export const OrgDirectoryResponseSchema = z.object({
|
|
|
820
820
|
export type OrgDirectoryResponse = z.infer<typeof OrgDirectoryResponseSchema>;
|
|
821
821
|
|
|
822
822
|
// ---------------------------------------------------------------------------
|
|
823
|
-
// POST /api/workspace/invites/batch
|
|
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.
|
|
824
836
|
//
|
|
825
837
|
// Bulk invite, keyed by `personId` rather than by email. That is a security
|
|
826
838
|
// property, not a convenience: the single-invite route takes a caller-supplied
|
|
@@ -842,6 +854,7 @@ export type OrgDirectoryResponse = z.infer<typeof OrgDirectoryResponseSchema>;
|
|
|
842
854
|
*/
|
|
843
855
|
export const BATCH_INVITE_PERSONS_MAX = 100;
|
|
844
856
|
|
|
857
|
+
/** @deprecated Use {@link CreateInviteBatchRequestSchema}. Removed in 58.0.0. */
|
|
845
858
|
export const BatchInviteRequestSchema = z.object({
|
|
846
859
|
personIds: z.array(z.string().uuid()).min(1).max(BATCH_INVITE_PERSONS_MAX),
|
|
847
860
|
// Same restricted {admin, member} domain as a single invite.
|
|
@@ -867,6 +880,7 @@ export const BATCH_INVITE_SKIP_REASONS = [
|
|
|
867
880
|
"send-failed",
|
|
868
881
|
] as const;
|
|
869
882
|
|
|
883
|
+
/** @deprecated Use {@link InviteBatchItemReasonSchema}. Removed in 58.0.0. */
|
|
870
884
|
export const BatchInviteSkipReasonSchema = z.enum(BATCH_INVITE_SKIP_REASONS);
|
|
871
885
|
|
|
872
886
|
export type BatchInviteSkipReason = z.infer<typeof BatchInviteSkipReasonSchema>;
|
|
@@ -876,6 +890,7 @@ const BatchInviteOutcomeSchema = z.object({
|
|
|
876
890
|
reason: BatchInviteSkipReasonSchema,
|
|
877
891
|
});
|
|
878
892
|
|
|
893
|
+
/** @deprecated Use {@link InviteBatchResponseSchema}. Removed in 58.0.0. */
|
|
879
894
|
export const BatchInviteResponseSchema = z.object({
|
|
880
895
|
/** Person ids that now hold a pending invite AND were emailed. */
|
|
881
896
|
invited: z.array(z.string().uuid()),
|
|
@@ -891,6 +906,309 @@ export const BatchInviteResponseSchema = z.object({
|
|
|
891
906
|
|
|
892
907
|
export type BatchInviteResponse = z.infer<typeof BatchInviteResponseSchema>;
|
|
893
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
|
+
|
|
894
1212
|
// ---------------------------------------------------------------------------
|
|
895
1213
|
// OrgDomain sub-schema
|
|
896
1214
|
// ---------------------------------------------------------------------------
|