@company-semantics/contracts 51.2.0 → 52.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": "51.2.0",
3
+ "version": "52.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 = 'b044ca016033' as const;
3
- export const SPEC_HASH_FULL = 'b044ca016033c3a7224160a73898e3f551218dbef0139a23601f1ca4eb171d15' as const;
2
+ export const SPEC_HASH = 'd0171aedf623' as const;
3
+ export const SPEC_HASH_FULL = 'd0171aedf623604dc721fb42b626ebc019afcc2fda9be59e1f06360cd6a4fcca' as const;
@@ -3616,6 +3616,23 @@ export interface paths {
3616
3616
  patch?: never;
3617
3617
  trace?: never;
3618
3618
  };
3619
+ "/api/comments/threads/{threadId}/accept-apply": {
3620
+ parameters: {
3621
+ query?: never;
3622
+ header?: never;
3623
+ path?: never;
3624
+ cookie?: never;
3625
+ };
3626
+ get?: never;
3627
+ put?: never;
3628
+ /** Apply a claimed suggestion and record the accept, on one commit */
3629
+ post: operations["acceptSuggestionApply"];
3630
+ delete?: never;
3631
+ options?: never;
3632
+ head?: never;
3633
+ patch?: never;
3634
+ trace?: never;
3635
+ };
3619
3636
  "/api/comments/threads/{threadId}/accept-release": {
3620
3637
  parameters: {
3621
3638
  query?: never;
@@ -6542,6 +6559,15 @@ export interface components {
6542
6559
  SuggestionClaimRequest: {
6543
6560
  claimToken: string;
6544
6561
  };
6562
+ /** @description The accepted thread and the document mutation that landed with it, on one commit. */
6563
+ SuggestionAcceptAppliedResponse: {
6564
+ thread: components["schemas"]["CommentThreadSummary"];
6565
+ applied: components["schemas"]["SuggestionApplied"];
6566
+ };
6567
+ SuggestionApplyRequest: {
6568
+ claimToken: string;
6569
+ expectedRevision: string;
6570
+ };
6545
6571
  /** @description Server-enforced collaboration size limits, published so clients can pre-check. */
6546
6572
  CollabSyncLimits: {
6547
6573
  maxUpdateBytes: number;
@@ -6712,6 +6738,17 @@ export interface components {
6712
6738
  createdAt: string;
6713
6739
  updatedAt: string;
6714
6740
  };
6741
+ /** @description What the accept-apply did to the document: whether there was a CRDT to write into at all, and the canonical edit that was spliced. */
6742
+ SuggestionApplied: {
6743
+ bridged: boolean;
6744
+ edit: components["schemas"]["SuggestionPlannedEdit"];
6745
+ };
6746
+ /** @description The canonical mutation derived from the stored payload: splice out deleteLength UTF-16 code units at `at` and insert `insert`. */
6747
+ SuggestionPlannedEdit: {
6748
+ at: number;
6749
+ deleteLength: number;
6750
+ insert: string;
6751
+ };
6715
6752
  };
6716
6753
  responses: never;
6717
6754
  parameters: never;
@@ -12979,6 +13016,53 @@ export interface operations {
12979
13016
  };
12980
13017
  };
12981
13018
  };
13019
+ acceptSuggestionApply: {
13020
+ parameters: {
13021
+ query?: never;
13022
+ header?: never;
13023
+ path: {
13024
+ threadId: string;
13025
+ };
13026
+ cookie?: never;
13027
+ };
13028
+ requestBody: {
13029
+ content: {
13030
+ "application/json": components["schemas"]["SuggestionApplyRequest"];
13031
+ };
13032
+ };
13033
+ responses: {
13034
+ /** @description Suggestion applied and accepted: the canonical edit landed in the document and the thread was resolved on the SAME transaction */
13035
+ 200: {
13036
+ headers: {
13037
+ [name: string]: unknown;
13038
+ };
13039
+ content: {
13040
+ "application/json": components["schemas"]["SuggestionAcceptAppliedResponse"];
13041
+ };
13042
+ };
13043
+ /** @description Malformed claim token or expectedRevision */
13044
+ 400: {
13045
+ headers: {
13046
+ [name: string]: unknown;
13047
+ };
13048
+ content?: never;
13049
+ };
13050
+ /** @description Thread not found, or the caller may not read its subject */
13051
+ 404: {
13052
+ headers: {
13053
+ [name: string]: unknown;
13054
+ };
13055
+ content?: never;
13056
+ };
13057
+ /** @description Terminal, never retry: not a suggestion thread, the token does not match a live claim, the document moved past expectedRevision, the anchor is orphaned or no longer reads, or the payload resolves to a no-op — re-read the document and decide again */
13058
+ 409: {
13059
+ headers: {
13060
+ [name: string]: unknown;
13061
+ };
13062
+ content?: never;
13063
+ };
13064
+ };
13065
+ };
12982
13066
  releaseSuggestionClaim: {
12983
13067
  parameters: {
12984
13068
  query?: never;
@@ -23,6 +23,7 @@ export const openApiRoutes = {
23
23
  '/api/chats/{id}/pin': ['DELETE', 'POST'],
24
24
  '/api/comments': ['GET'],
25
25
  '/api/comments/threads': ['POST'],
26
+ '/api/comments/threads/{threadId}/accept-apply': ['POST'],
26
27
  '/api/comments/threads/{threadId}/accept-complete': ['POST'],
27
28
  '/api/comments/threads/{threadId}/accept-intent': ['POST'],
28
29
  '/api/comments/threads/{threadId}/accept-release': ['POST'],
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import {
4
+ PeopleOrgChartSeatOccupantSchema,
5
+ PeopleOrgChartSeatSchema,
6
+ } from "../people-org-chart.js";
7
+
8
+ const PERSON_ID = "3f2504e0-4f89-41d3-9a0c-0305e82c3301";
9
+ const USER_ID = "9c5b94b1-35ad-49bb-b118-8e8fc24abf80";
10
+ const UNIT_ID = "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b";
11
+ const POSITION_ID = "11111111-1111-4111-8111-111111111111";
12
+
13
+ describe("PeopleOrgChartSeatOccupantSchema", () => {
14
+ const accountless = {
15
+ personId: PERSON_ID,
16
+ userId: null,
17
+ fullName: "Ada Lovelace",
18
+ avatarUrl: null,
19
+ primaryUnitId: null,
20
+ ownedUnits: [],
21
+ };
22
+
23
+ it("parses an accountless occupant keyed by personId", () => {
24
+ const occupant = PeopleOrgChartSeatOccupantSchema.parse(accountless);
25
+ expect(occupant.personId).toBe(PERSON_ID);
26
+ expect(occupant.userId).toBeNull();
27
+ });
28
+
29
+ it("parses an account-linked occupant carrying a uuid userId", () => {
30
+ const occupant = PeopleOrgChartSeatOccupantSchema.parse({
31
+ ...accountless,
32
+ userId: USER_ID,
33
+ });
34
+ expect(occupant.userId).toBe(USER_ID);
35
+ });
36
+
37
+ it("requires personId", () => {
38
+ const { personId: _omitted, ...withoutPersonId } = accountless;
39
+ expect(() =>
40
+ PeopleOrgChartSeatOccupantSchema.parse(withoutPersonId),
41
+ ).toThrow();
42
+ });
43
+
44
+ // userId is .nullable() WITHOUT .optional(): the server always emits the key,
45
+ // so an absent userId must be unrepresentable rather than silently null.
46
+ it("rejects an absent userId — nullable is not optional", () => {
47
+ const { userId: _omitted, ...withoutUserId } = accountless;
48
+ const result = PeopleOrgChartSeatOccupantSchema.safeParse(withoutUserId);
49
+ expect(result.success).toBe(false);
50
+ });
51
+ });
52
+
53
+ describe("PeopleOrgChartSeatSchema", () => {
54
+ it("admits a filled seat whose only occupant is accountless", () => {
55
+ const seat = PeopleOrgChartSeatSchema.parse({
56
+ id: POSITION_ID,
57
+ unitId: UNIT_ID,
58
+ title: "Analyst",
59
+ status: "filled",
60
+ hiring: false,
61
+ occupants: [
62
+ {
63
+ personId: PERSON_ID,
64
+ userId: null,
65
+ fullName: "Ada Lovelace",
66
+ avatarUrl: null,
67
+ primaryUnitId: null,
68
+ ownedUnits: [],
69
+ },
70
+ ],
71
+ });
72
+ expect(seat.occupants).toHaveLength(1);
73
+ expect(seat.occupants[0]?.userId).toBeNull();
74
+ });
75
+ });
@@ -88,11 +88,30 @@ export type PeopleOrgChartSeatStatus = z.infer<
88
88
  typeof PeopleOrgChartSeatStatusSchema
89
89
  >;
90
90
 
91
- // Person-scoped attributes of a seat's holder — keyed by `userId`, they travel
92
- // with the person across seats (ADR-BE-329: a vacant seat grants none of these
93
- // by construction). Mirrors the legacy `PeopleOrgChartNode` person fields.
91
+ // Person-scoped attributes of a seat's holder — they travel with the person
92
+ // across seats (ADR-BE-329: a vacant seat grants none of these by
93
+ // construction).
94
+ //
95
+ // KEYED BY personId. The org chart is a graph of POSITIONS with PEOPLE
96
+ // projected onto them; the account link is an attribute of the person, never
97
+ // their identity. `userId` is the 0..1 account link and is null for the many
98
+ // people who never sign in (an HRIS import is the normal case, not the edge).
99
+ //
100
+ // For an ACCOUNTLESS occupant each of the following is a FACT, not a fallback:
101
+ // avatarUrl always null -- `persons` carries no avatar column
102
+ // primaryUnitId always null -- `home_assignments` is user-keyed, so there is
103
+ // no representable home; null means "no home
104
+ // row", NOT "home unknown, substitute the seat"
105
+ // ownedUnits always [] -- the projection of "no grant row exists".
106
+ // Authority is account-keyed; an accountless
107
+ // person holds none. This is the authority
108
+ // boundary made visible.
94
109
  export const PeopleOrgChartSeatOccupantSchema = z.object({
95
- userId: z.string().uuid(),
110
+ personId: z.string().uuid(),
111
+ // .nullable() WITHOUT .optional(): this is a server-generated response, so
112
+ // `undefined` must be unrepresentable. PersonSchema.userId is
113
+ // .nullable().optional() because it is an input-shaped vocabulary.
114
+ userId: z.string().uuid().nullable(),
96
115
  fullName: z.string(),
97
116
  avatarUrl: z.string().nullable(),
98
117
  // Stable HOME unit (home_assignments), not the seat unit — mirrors the legacy
@@ -123,8 +142,8 @@ export const PeopleOrgChartSeatSchema = z.object({
123
142
  // fulfillment row ⇒ false.
124
143
  hiring: z.boolean(),
125
144
  // The seat's current holders. [] ⇒ vacant (the old "open role"); >= 1 ⇒
126
- // occupied (the old "member"). Only account-linked, active/interim occupants
127
- // appear, mirroring the legacy node-keying.
145
+ // occupied (the old "member"). Every canonical occupant appears, account or
146
+ // not — occupancy, not account linkage, is what admits a person here.
128
147
  occupants: z.array(PeopleOrgChartSeatOccupantSchema),
129
148
  });
130
149