@company-semantics/contracts 21.1.0 → 21.2.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/org/README.md +4 -0
- package/src/org/__tests__/invite-schemas.test.ts +104 -0
- package/src/org/index.ts +4 -0
- package/src/org/schemas.ts +71 -0
- package/src/org/types.ts +44 -0
package/package.json
CHANGED
package/src/org/README.md
CHANGED
|
@@ -75,6 +75,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
75
75
|
- `CreateDelegationRequest` _(type)_
|
|
76
76
|
- `CreateDelegationRequestSchema`
|
|
77
77
|
- `CreateInviteRequest` _(type)_ — Request payload for creating an organization invite.
|
|
78
|
+
- `CreateInviteRequestPayload` _(type)_
|
|
79
|
+
- `CreateInviteRequestSchema`
|
|
78
80
|
- `CreateOpenRoleRequest` _(type)_
|
|
79
81
|
- `CreateOpenRoleRequestSchema`
|
|
80
82
|
- `Delegation` _(type)_
|
|
@@ -258,6 +260,8 @@ Shared type vocabulary for organization ownership, type classification, and tran
|
|
|
258
260
|
- `SsoReadinessCheck` _(type)_ — Individual readiness check for SSO activation.
|
|
259
261
|
- `SsoSetupInfo` _(type)_ — SSO setup information for admin configuration UI.
|
|
260
262
|
- `SsoStepperStep` _(type)_ — Backend-authoritative stepper step.
|
|
263
|
+
- `StagedPlacement` _(type)_ — Staged member placement: the fuller org position an invitee is slotted into, applied as deferred grants when…
|
|
264
|
+
- `StagedPlacementSchema` — Staged member placement: the fuller org position an invitee is slotted into, applied as deferred grants when…
|
|
261
265
|
- `SubmitInteractiveTaskResponse` _(type)_
|
|
262
266
|
- `SubmitInteractiveTaskResponseSchema` — Response for submitting a filled-in interactive chat task surface (e.g. the editable "change reporting…
|
|
263
267
|
- `SyncRunSummary` _(type)_
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
StagedPlacementSchema,
|
|
4
|
+
CreateInviteRequestSchema,
|
|
5
|
+
InviteResponseSchema,
|
|
6
|
+
} from "../schemas.js";
|
|
7
|
+
|
|
8
|
+
const UUID_A = "11111111-1111-4111-8111-111111111111";
|
|
9
|
+
const UUID_B = "22222222-2222-4222-8222-222222222222";
|
|
10
|
+
const UUID_C = "33333333-3333-4333-8333-333333333333";
|
|
11
|
+
|
|
12
|
+
const makeInvite = (overrides: Record<string, unknown> = {}) => ({
|
|
13
|
+
id: UUID_A,
|
|
14
|
+
orgId: UUID_B,
|
|
15
|
+
email: "new.hire@example.com",
|
|
16
|
+
role: "member",
|
|
17
|
+
invitedBy: { id: UUID_C, name: "Inviter" },
|
|
18
|
+
status: "pending",
|
|
19
|
+
createdAt: "2026-06-24T00:00:00Z",
|
|
20
|
+
expiresAt: "2026-07-01T00:00:00Z",
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("CreateInviteRequestSchema", () => {
|
|
25
|
+
it("parses a minimal payload without stagedPlacement", () => {
|
|
26
|
+
const parsed = CreateInviteRequestSchema.parse({
|
|
27
|
+
email: "new.hire@example.com",
|
|
28
|
+
role: "admin",
|
|
29
|
+
});
|
|
30
|
+
expect(parsed.stagedPlacement).toBeUndefined();
|
|
31
|
+
expect(parsed.homeUnitId).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("parses a full stagedPlacement with arrays defaulting to []", () => {
|
|
35
|
+
const parsed = CreateInviteRequestSchema.parse({
|
|
36
|
+
email: "new.hire@example.com",
|
|
37
|
+
role: "member",
|
|
38
|
+
homeUnitId: UUID_A,
|
|
39
|
+
homeUnitRole: "unit_owner",
|
|
40
|
+
stagedPlacement: {
|
|
41
|
+
title: "Staff Engineer",
|
|
42
|
+
managerUserId: UUID_B,
|
|
43
|
+
homeUnitId: UUID_A,
|
|
44
|
+
homeUnitRole: "unit_owner",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(parsed.stagedPlacement?.title).toBe("Staff Engineer");
|
|
48
|
+
// unitOwnerOf/contributesTo default to [] when omitted.
|
|
49
|
+
expect(parsed.stagedPlacement?.unitOwnerOf).toEqual([]);
|
|
50
|
+
expect(parsed.stagedPlacement?.contributesTo).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("StagedPlacementSchema", () => {
|
|
55
|
+
it("defaults unitOwnerOf and contributesTo to []", () => {
|
|
56
|
+
const parsed = StagedPlacementSchema.parse({});
|
|
57
|
+
expect(parsed.unitOwnerOf).toEqual([]);
|
|
58
|
+
expect(parsed.contributesTo).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("retains provided unit arrays", () => {
|
|
62
|
+
const parsed = StagedPlacementSchema.parse({
|
|
63
|
+
unitOwnerOf: [UUID_A],
|
|
64
|
+
contributesTo: [UUID_B, UUID_C],
|
|
65
|
+
});
|
|
66
|
+
expect(parsed.unitOwnerOf).toEqual([UUID_A]);
|
|
67
|
+
expect(parsed.contributesTo).toEqual([UUID_B, UUID_C]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("trims the title", () => {
|
|
71
|
+
const parsed = StagedPlacementSchema.parse({ title: " Lead " });
|
|
72
|
+
expect(parsed.title).toBe("Lead");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("rejects a non-uuid managerUserId", () => {
|
|
76
|
+
expect(() =>
|
|
77
|
+
StagedPlacementSchema.parse({ managerUserId: "not-a-uuid" }),
|
|
78
|
+
).toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("OrgInviteSchema (via InviteResponseSchema)", () => {
|
|
83
|
+
it("parses an invite without stagedPlacement (back-compat)", () => {
|
|
84
|
+
const parsed = InviteResponseSchema.parse({ invite: makeInvite() });
|
|
85
|
+
expect(parsed.invite.stagedPlacement).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("parses an invite carrying a stagedPlacement", () => {
|
|
89
|
+
const parsed = InviteResponseSchema.parse({
|
|
90
|
+
invite: makeInvite({
|
|
91
|
+
homeUnitId: UUID_A,
|
|
92
|
+
homeUnitRole: "member",
|
|
93
|
+
stagedPlacement: {
|
|
94
|
+
title: "Engineer",
|
|
95
|
+
unitOwnerOf: [UUID_A],
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
expect(parsed.invite.stagedPlacement?.title).toBe("Engineer");
|
|
100
|
+
expect(parsed.invite.stagedPlacement?.unitOwnerOf).toEqual([UUID_A]);
|
|
101
|
+
// Omitted contributesTo still defaults to [].
|
|
102
|
+
expect(parsed.invite.stagedPlacement?.contributesTo).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
});
|
package/src/org/index.ts
CHANGED
|
@@ -249,6 +249,8 @@ export type {
|
|
|
249
249
|
|
|
250
250
|
// Org lifecycle response schemas (PRD-00446)
|
|
251
251
|
export {
|
|
252
|
+
StagedPlacementSchema,
|
|
253
|
+
CreateInviteRequestSchema,
|
|
252
254
|
InviteResponseSchema,
|
|
253
255
|
InviteListResponseSchema,
|
|
254
256
|
DomainResponseSchema,
|
|
@@ -259,6 +261,8 @@ export {
|
|
|
259
261
|
OwnershipTransferPreviewSchema,
|
|
260
262
|
} from "./schemas";
|
|
261
263
|
export type {
|
|
264
|
+
StagedPlacement,
|
|
265
|
+
CreateInviteRequestPayload,
|
|
262
266
|
InviteResponse,
|
|
263
267
|
InviteListResponse,
|
|
264
268
|
DomainResponse,
|
package/src/org/schemas.ts
CHANGED
|
@@ -606,6 +606,46 @@ export type ScopeCheckBatchResponse = z.infer<
|
|
|
606
606
|
export const HomeUnitRoleSchema = z.enum(["member", "unit_owner"]);
|
|
607
607
|
export type HomeUnitRole = z.infer<typeof HomeUnitRoleSchema>;
|
|
608
608
|
|
|
609
|
+
/** Established title cap shared with org-unit/open-role titles. */
|
|
610
|
+
const STAGED_PLACEMENT_TITLE_MAX = 255;
|
|
611
|
+
/**
|
|
612
|
+
* Upper bound on the number of units an invitee can be staged into as owner or
|
|
613
|
+
* contributor. Bounds the replay cost of applying a staged placement at accept
|
|
614
|
+
* time (each entry is a deferred membership grant). A generous-but-finite cap.
|
|
615
|
+
*/
|
|
616
|
+
const STAGED_PLACEMENT_UNITS_MAX = 64;
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Staged member placement: the fuller org position an invitee is slotted into,
|
|
620
|
+
* applied as deferred grants when the invite is accepted. A superset of the
|
|
621
|
+
* legacy top-level `homeUnitId`/`homeUnitRole` fields, carrying the (optional)
|
|
622
|
+
* intended title, manager, and the units the invitee will own or contribute to.
|
|
623
|
+
*
|
|
624
|
+
* Every field is optional or defaulted so a placement can be partially
|
|
625
|
+
* specified at invite time. The array caps bound downstream replay cost.
|
|
626
|
+
*/
|
|
627
|
+
export const StagedPlacementSchema = z.object({
|
|
628
|
+
// Intended job/role title for the invitee. Trimmed; non-empty when present.
|
|
629
|
+
title: z.string().trim().min(1).max(STAGED_PLACEMENT_TITLE_MAX).optional(),
|
|
630
|
+
// The invitee's manager on acceptance (a `manages` designation).
|
|
631
|
+
managerUserId: z.string().uuid().optional(),
|
|
632
|
+
// Home unit the invitee is placed in (their `users.primary_unit_id`).
|
|
633
|
+
homeUnitId: z.string().uuid().optional(),
|
|
634
|
+
// Role the invitee takes in `homeUnitId` ('unit_owner' = deferred grant).
|
|
635
|
+
homeUnitRole: HomeUnitRoleSchema.optional(),
|
|
636
|
+
// Additional units the invitee becomes a unit owner of on acceptance.
|
|
637
|
+
unitOwnerOf: z
|
|
638
|
+
.array(z.string().uuid())
|
|
639
|
+
.max(STAGED_PLACEMENT_UNITS_MAX)
|
|
640
|
+
.default([]),
|
|
641
|
+
// Units the invitee contributes to (non-owner membership) on acceptance.
|
|
642
|
+
contributesTo: z
|
|
643
|
+
.array(z.string().uuid())
|
|
644
|
+
.max(STAGED_PLACEMENT_UNITS_MAX)
|
|
645
|
+
.default([]),
|
|
646
|
+
});
|
|
647
|
+
export type StagedPlacement = z.infer<typeof StagedPlacementSchema>;
|
|
648
|
+
|
|
609
649
|
const OrgInviteSchema = z.object({
|
|
610
650
|
id: z.string(),
|
|
611
651
|
orgId: z.string(),
|
|
@@ -620,6 +660,10 @@ const OrgInviteSchema = z.object({
|
|
|
620
660
|
// deferred unit-ownership grant). Optional/'member' for legacy +
|
|
621
661
|
// unit-less invites.
|
|
622
662
|
homeUnitRole: HomeUnitRoleSchema.optional(),
|
|
663
|
+
// Fuller staged placement applied as deferred grants on acceptance. A
|
|
664
|
+
// superset of the top-level homeUnitId/homeUnitRole, kept additive/optional
|
|
665
|
+
// so legacy invites and unit-less invites parse unchanged.
|
|
666
|
+
stagedPlacement: StagedPlacementSchema.optional(),
|
|
623
667
|
createdAt: z.string(),
|
|
624
668
|
expiresAt: z.string(),
|
|
625
669
|
acceptedAt: z.string().optional(),
|
|
@@ -643,6 +687,33 @@ export const InviteListResponseSchema = z.array(OrgInviteSchema);
|
|
|
643
687
|
|
|
644
688
|
export type InviteListResponse = z.infer<typeof InviteListResponseSchema>;
|
|
645
689
|
|
|
690
|
+
// ---------------------------------------------------------------------------
|
|
691
|
+
// POST /api/workspace/invites — request body
|
|
692
|
+
// Request schema colocated with the response schema (narrow deviation from
|
|
693
|
+
// ADR-CONT-044, per the CreateOpenRoleRequestSchema precedent) because the
|
|
694
|
+
// staged-placement shape is the published promise consumers must satisfy.
|
|
695
|
+
// Mirrors the hand-written `CreateInviteRequest` interface in org/types.ts and
|
|
696
|
+
// extends it with the optional `stagedPlacement` superset.
|
|
697
|
+
// ---------------------------------------------------------------------------
|
|
698
|
+
|
|
699
|
+
export const CreateInviteRequestSchema = z.object({
|
|
700
|
+
email: z.string().email(),
|
|
701
|
+
// Invites only grant the restricted {admin, member} domain.
|
|
702
|
+
role: z.enum(["admin", "member"]),
|
|
703
|
+
// Home unit the invitee is placed in on acceptance. Optional in the contract
|
|
704
|
+
// so the field can roll out without breaking older callers.
|
|
705
|
+
homeUnitId: z.string().uuid().optional(),
|
|
706
|
+
// Role the invitee takes in homeUnitId — only meaningful with a home unit.
|
|
707
|
+
homeUnitRole: HomeUnitRoleSchema.optional(),
|
|
708
|
+
// Fuller staged placement (title, manager, owned/contributed units) applied
|
|
709
|
+
// as deferred grants on acceptance. Optional/additive for back-compat.
|
|
710
|
+
stagedPlacement: StagedPlacementSchema.optional(),
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
export type CreateInviteRequestPayload = z.infer<
|
|
714
|
+
typeof CreateInviteRequestSchema
|
|
715
|
+
>;
|
|
716
|
+
|
|
646
717
|
// ---------------------------------------------------------------------------
|
|
647
718
|
// OrgDomain sub-schema
|
|
648
719
|
// ---------------------------------------------------------------------------
|
package/src/org/types.ts
CHANGED
|
@@ -397,6 +397,37 @@ export interface WorkspaceAuditEvent {
|
|
|
397
397
|
*/
|
|
398
398
|
export type OrgInviteStatus = "pending" | "accepted" | "expired" | "revoked";
|
|
399
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Staged member placement: the fuller org position an invitee is slotted into,
|
|
402
|
+
* applied as deferred grants when the invite is accepted. A superset of the
|
|
403
|
+
* top-level `homeUnitId`/`homeUnitRole` fields, carrying the (optional) intended
|
|
404
|
+
* title, manager, and the units the invitee will own or contribute to.
|
|
405
|
+
*
|
|
406
|
+
* Hand-written mirror of `StagedPlacementSchema` in `org/schemas.ts` — keep in
|
|
407
|
+
* lockstep. Because the schema defaults `unitOwnerOf`/`contributesTo` to `[]`,
|
|
408
|
+
* their inferred OUTPUT type is a required `string[]`; the other four fields are
|
|
409
|
+
* optional. The `homeUnitRole` union is re-typed inline (not imported) to match
|
|
410
|
+
* the `OrgInvite`/`CreateInviteRequest` convention.
|
|
411
|
+
*
|
|
412
|
+
* NOTE: the canonical `StagedPlacement` name is exported package-root from
|
|
413
|
+
* `org/schemas.ts` (the `z.infer`). This interface is intentionally NOT added to
|
|
414
|
+
* the `./types` re-export block in `org/index.ts` to avoid a name collision.
|
|
415
|
+
*/
|
|
416
|
+
export interface StagedPlacement {
|
|
417
|
+
/** Intended job/role title for the invitee. Trimmed; non-empty when present. */
|
|
418
|
+
title?: string;
|
|
419
|
+
/** The invitee's manager on acceptance (a `manages` designation) — a member id. */
|
|
420
|
+
managerUserId?: string;
|
|
421
|
+
/** Home unit the invitee is placed in (their `users.primary_unit_id`). */
|
|
422
|
+
homeUnitId?: string;
|
|
423
|
+
/** Role the invitee takes in `homeUnitId` (`unit_owner` = deferred grant). */
|
|
424
|
+
homeUnitRole?: "member" | "unit_owner";
|
|
425
|
+
/** Additional units the invitee becomes a unit owner of on acceptance. */
|
|
426
|
+
unitOwnerOf: string[];
|
|
427
|
+
/** Units the invitee contributes to (non-owner membership) on acceptance. */
|
|
428
|
+
contributesTo: string[];
|
|
429
|
+
}
|
|
430
|
+
|
|
400
431
|
/**
|
|
401
432
|
* Organization invite for the workspace invites list.
|
|
402
433
|
* Represents a pending or historical invitation.
|
|
@@ -425,6 +456,13 @@ export interface OrgInvite {
|
|
|
425
456
|
* legacy invites and unit-less invites have none (treated as `member`).
|
|
426
457
|
*/
|
|
427
458
|
homeUnitRole?: "member" | "unit_owner";
|
|
459
|
+
/**
|
|
460
|
+
* Fuller staged placement applied as deferred grants on acceptance. A superset
|
|
461
|
+
* of the top-level `homeUnitId`/`homeUnitRole`, kept additive/optional so
|
|
462
|
+
* legacy and unit-less invites parse unchanged. Mirrors
|
|
463
|
+
* `OrgInviteSchema.stagedPlacement` in `org/schemas.ts`.
|
|
464
|
+
*/
|
|
465
|
+
stagedPlacement?: StagedPlacement;
|
|
428
466
|
createdAt: string;
|
|
429
467
|
expiresAt: string;
|
|
430
468
|
acceptedAt?: string;
|
|
@@ -450,6 +488,12 @@ export interface CreateInviteRequest {
|
|
|
450
488
|
* rejects `unit_owner` without a home unit. Optional/`member` for older callers.
|
|
451
489
|
*/
|
|
452
490
|
homeUnitRole?: "member" | "unit_owner";
|
|
491
|
+
/**
|
|
492
|
+
* Fuller staged placement (title, manager, owned/contributed units) applied as
|
|
493
|
+
* deferred grants on acceptance. Optional/additive for back-compat. Mirrors
|
|
494
|
+
* `CreateInviteRequestSchema.stagedPlacement` in `org/schemas.ts`.
|
|
495
|
+
*/
|
|
496
|
+
stagedPlacement?: StagedPlacement;
|
|
453
497
|
}
|
|
454
498
|
|
|
455
499
|
/**
|