@company-semantics/contracts 18.2.0 → 19.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "18.2.0",
3
+ "version": "19.0.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '4944d64336bd' as const;
3
- export const SPEC_HASH_FULL = '4944d64336bd97c454ceef9917209bd1da7bab66a771e02c91bafec5ab31d47e' as const;
2
+ export const SPEC_HASH = '814c99a18543' as const;
3
+ export const SPEC_HASH_FULL = '814c99a18543f448daaed5312db276f7704a9b63710c512b1a298be5fc652105' as const;
@@ -4745,6 +4745,11 @@ export interface components {
4745
4745
  jobTitle: string | null;
4746
4746
  avatarUrl: string | null;
4747
4747
  primaryUnitId: string | null;
4748
+ ownedUnits: {
4749
+ /** Format: uuid */
4750
+ id: string;
4751
+ name: string;
4752
+ }[];
4748
4753
  }[];
4749
4754
  edges: {
4750
4755
  /** Format: uuid */
@@ -44,6 +44,8 @@ TypeScript types and functions for user identity and display name resolution.
44
44
  - `BannerDismissedListResponseSchema`
45
45
  - `DeletionBlocker` _(type)_ — Conditions that block account deletion.
46
46
  - `ISODateString` _(type)_ — ISO 8601 date-time string.
47
+ - `IdentityLink` _(type)_
48
+ - `IdentityLinkSchema`
47
49
  - `MeResponse` _(type)_
48
50
  - `MeResponseSchema` — Full identity context returned by GET /api/me.
49
51
  - `NameSource` _(type)_ — Source of the user's name data.
@@ -53,8 +55,12 @@ TypeScript types and functions for user identity and display name resolution.
53
55
  - `PeopleOrgChartNodeSchema`
54
56
  - `PeopleOrgChartOpenRole` _(type)_
55
57
  - `PeopleOrgChartOpenRoleSchema`
58
+ - `PeopleOrgChartOwnedUnit` _(type)_
59
+ - `PeopleOrgChartOwnedUnitSchema`
56
60
  - `PeopleOrgChartResponse` _(type)_
57
61
  - `PeopleOrgChartResponseSchema`
62
+ - `Person` _(type)_
63
+ - `PersonSchema`
58
64
  - `ProfileResponse` _(type)_
59
65
  - `ProfileResponseSchema`
60
66
  - `ReportingRelationshipType` _(type)_
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { IdentityLinkSchema } from "../identity-link.js";
3
+
4
+ const validLink = {
5
+ id: "11111111-1111-4111-8111-111111111111",
6
+ orgId: "22222222-2222-4222-8222-222222222222",
7
+ provider: "bamboohr",
8
+ externalId: "emp-4081",
9
+ personId: "33333333-3333-4333-8333-333333333333",
10
+ createdAt: "2026-06-21T10:00:00.000Z",
11
+ updatedAt: "2026-06-21T10:00:00.000Z",
12
+ };
13
+
14
+ describe("IdentityLinkSchema", () => {
15
+ it("accepts a valid identity link record", () => {
16
+ expect(IdentityLinkSchema.parse(validLink)).toEqual(validLink);
17
+ });
18
+
19
+ it("accepts arbitrary provider strings (open vocabulary)", () => {
20
+ for (const provider of ["bamboohr", "workday", "scim", "custom-hris"]) {
21
+ expect(
22
+ IdentityLinkSchema.safeParse({ ...validLink, provider }).success,
23
+ ).toBe(true);
24
+ }
25
+ });
26
+
27
+ it("rejects an empty provider", () => {
28
+ expect(
29
+ IdentityLinkSchema.safeParse({ ...validLink, provider: "" }).success,
30
+ ).toBe(false);
31
+ });
32
+
33
+ it("rejects an empty externalId", () => {
34
+ expect(
35
+ IdentityLinkSchema.safeParse({ ...validLink, externalId: "" }).success,
36
+ ).toBe(false);
37
+ });
38
+
39
+ it("rejects a non-uuid personId", () => {
40
+ expect(
41
+ IdentityLinkSchema.safeParse({ ...validLink, personId: "not-a-uuid" })
42
+ .success,
43
+ ).toBe(false);
44
+ });
45
+
46
+ it("rejects a non-datetime createdAt", () => {
47
+ expect(
48
+ IdentityLinkSchema.safeParse({ ...validLink, createdAt: "yesterday" })
49
+ .success,
50
+ ).toBe(false);
51
+ });
52
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { PersonSchema } from "../person.js";
4
+
5
+ describe("PersonSchema", () => {
6
+ const base = {
7
+ id: "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
8
+ orgId: "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
9
+ displayName: "Ada Lovelace",
10
+ };
11
+
12
+ it("parses a minimal accountless person", () => {
13
+ const person = PersonSchema.parse(base);
14
+ expect(person.displayName).toBe("Ada Lovelace");
15
+ expect(person.userId).toBeUndefined();
16
+ });
17
+
18
+ it("accepts a null userId (accountless) and a uuid userId (linked)", () => {
19
+ expect(PersonSchema.parse({ ...base, userId: null }).userId).toBeNull();
20
+ const linked = PersonSchema.parse({
21
+ ...base,
22
+ userId: "9c5b94b1-35ad-49bb-b118-8e8fc24abf80",
23
+ });
24
+ expect(linked.userId).toBe("9c5b94b1-35ad-49bb-b118-8e8fc24abf80");
25
+ });
26
+
27
+ it("accepts a null primaryEmail and validates email format", () => {
28
+ expect(
29
+ PersonSchema.parse({ ...base, primaryEmail: null }).primaryEmail,
30
+ ).toBeNull();
31
+ expect(() =>
32
+ PersonSchema.parse({ ...base, primaryEmail: "not-an-email" }),
33
+ ).toThrow();
34
+ });
35
+
36
+ it("rejects an empty displayName and non-uuid id", () => {
37
+ expect(() => PersonSchema.parse({ ...base, displayName: "" })).toThrow();
38
+ expect(() => PersonSchema.parse({ ...base, id: "nope" })).toThrow();
39
+ });
40
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Identity Link Vocabulary
3
+ *
4
+ * An IdentityLink is a durable map from an external system's identifier to a
5
+ * Person in the org graph. It generalizes the legacy
6
+ * `users.external_source_id` / `users.external_source_system` pair into a
7
+ * first-class, per-source record: a BambooHR link is simply an IdentityLink
8
+ * with `provider: 'bamboohr'`.
9
+ *
10
+ * `provider` is an OPEN string (e.g. 'bamboohr', 'workday', 'scim') rather than
11
+ * an enum, so onboarding a new identity source needs no schema change.
12
+ *
13
+ * Logical uniqueness is the tuple (orgId, provider, externalId): a given
14
+ * external id from a given provider, within a given org, maps to exactly one
15
+ * Person. The (orgId, provider, externalId) tuple is the natural key.
16
+ *
17
+ * @see ADR-CONT-032 for identity vocabulary design rationale
18
+ */
19
+ import { z } from "zod";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // IdentityLink — external system id → Person
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export const IdentityLinkSchema = z.object({
26
+ id: z.string().uuid(),
27
+ orgId: z.string().uuid(),
28
+ // Open string (NOT an enum) so new sources need no schema change.
29
+ provider: z.string().min(1),
30
+ // The opaque identifier the external system uses for this person.
31
+ externalId: z.string().min(1),
32
+ // The Person this external identity resolves to (Person.id).
33
+ personId: z.string().uuid(),
34
+ createdAt: z.string().datetime(),
35
+ updatedAt: z.string().datetime(),
36
+ });
37
+
38
+ export type IdentityLink = z.infer<typeof IdentityLinkSchema>;
@@ -65,6 +65,7 @@ export type {
65
65
  export {
66
66
  ReportingRelationshipTypeSchema,
67
67
  PeopleOrgChartNodeSchema,
68
+ PeopleOrgChartOwnedUnitSchema,
68
69
  PeopleOrgChartEdgeSchema,
69
70
  PeopleOrgChartOpenRoleSchema,
70
71
  PeopleOrgChartResponseSchema,
@@ -72,7 +73,16 @@ export {
72
73
  export type {
73
74
  ReportingRelationshipType,
74
75
  PeopleOrgChartNode,
76
+ PeopleOrgChartOwnedUnit,
75
77
  PeopleOrgChartEdge,
76
78
  PeopleOrgChartOpenRole,
77
79
  PeopleOrgChartResponse,
78
80
  } from "./people-org-chart";
81
+
82
+ // Person — the org-graph node for a human (0..1 User link), per ADR-CTRL-182
83
+ export { PersonSchema } from "./person";
84
+ export type { Person } from "./person";
85
+
86
+ // Identity Link — external system id → Person (provider is an open string)
87
+ export { IdentityLinkSchema } from "./identity-link";
88
+ export type { IdentityLink } from "./identity-link";
@@ -26,12 +26,30 @@ export type ReportingRelationshipType = z.infer<
26
26
  // Node — a person in the chart
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
+ // A unit this person is a *local structural owner* of — i.e. holds a grant with
30
+ // `authority_source = 'structural'` directly on that unit (ADR-BE-168). Carries
31
+ // the unit name so the chart can render an owner tooltip ("Unit owner of …")
32
+ // without a second lookup. The org root unit appears here for the account
33
+ // owner(s) (the CEO), whom the chart marks with the owner icon instead.
34
+ export const PeopleOrgChartOwnedUnitSchema = z.object({
35
+ id: z.string().uuid(),
36
+ name: z.string(),
37
+ });
38
+
39
+ export type PeopleOrgChartOwnedUnit = z.infer<
40
+ typeof PeopleOrgChartOwnedUnitSchema
41
+ >;
42
+
29
43
  export const PeopleOrgChartNodeSchema = z.object({
30
44
  id: z.string().uuid(),
31
45
  fullName: z.string(),
32
46
  jobTitle: z.string().nullable(),
33
47
  avatarUrl: z.string().nullable(),
34
48
  primaryUnitId: z.string().uuid().nullable(),
49
+ // Units this person is a local structural owner of. Empty ⇒ not a unit owner.
50
+ // Drives the chart's owner key + tooltip; the account owner (root-unit owner)
51
+ // is rendered with the org owner icon (crown) instead.
52
+ ownedUnits: z.array(PeopleOrgChartOwnedUnitSchema),
35
53
  });
36
54
 
37
55
  export type PeopleOrgChartNode = z.infer<typeof PeopleOrgChartNodeSchema>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Person Identity Vocabulary
3
+ *
4
+ * Person: the human in the org — the org-graph node. Distinct from `User` (an
5
+ * authenticated account). A Person has 0..1 linked User (`userId`); accountless
6
+ * People are valid from day one. Identity vocabulary, NOT a structure fact, so it
7
+ * carries no FactProvenance envelope. See control ADR-CTRL-182.
8
+ */
9
+ import { z } from "zod";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Person — the org-graph node for a human, with an optional 0..1 User link
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export const PersonSchema = z.object({
16
+ /** Unique identifier for the person within the org graph. */
17
+ id: z.string().uuid(),
18
+ /** Owning organization (tenant) this person belongs to. */
19
+ orgId: z.string().uuid(),
20
+ /** Human-facing name of the person in the org. */
21
+ displayName: z.string().min(1),
22
+ /** Primary email if known; null for people imported without one. */
23
+ primaryEmail: z.string().email().nullable().optional(),
24
+ /** The authenticated account this person is, if any (0..1). Null when accountless. */
25
+ userId: z.string().uuid().nullable().optional(),
26
+ });
27
+
28
+ export type Person = z.infer<typeof PersonSchema>;
package/src/index.ts CHANGED
@@ -171,6 +171,7 @@ export type {
171
171
  export {
172
172
  ReportingRelationshipTypeSchema,
173
173
  PeopleOrgChartNodeSchema,
174
+ PeopleOrgChartOwnedUnitSchema,
174
175
  PeopleOrgChartEdgeSchema,
175
176
  PeopleOrgChartOpenRoleSchema,
176
177
  PeopleOrgChartResponseSchema,
@@ -178,11 +179,20 @@ export {
178
179
  export type {
179
180
  ReportingRelationshipType,
180
181
  PeopleOrgChartNode,
182
+ PeopleOrgChartOwnedUnit,
181
183
  PeopleOrgChartEdge,
182
184
  PeopleOrgChartOpenRole,
183
185
  PeopleOrgChartResponse,
184
186
  } from "./identity/index";
185
187
 
188
+ // Person — the org-graph node for a human (0..1 User link), per ADR-CTRL-182
189
+ export { PersonSchema } from "./identity/index";
190
+ export type { Person } from "./identity/index";
191
+
192
+ // Identity Link — external system id → Person (provider is an open string)
193
+ export { IdentityLinkSchema } from "./identity/index";
194
+ export type { IdentityLink } from "./identity/index";
195
+
186
196
  // Auth domain types
187
197
  export type { AuthStartMode, AuthStartResponse } from "./auth/index";
188
198
  export { OTPErrorCode } from "./auth/index";