@company-semantics/contracts 63.1.0 → 63.3.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": "63.1.0",
3
+ "version": "63.3.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -118,7 +118,7 @@
118
118
  "anchor-corpus:sync": "tsx scripts/anchor-corpus.ts --sync",
119
119
  "generate:spec-hash": "tsx scripts/generate-spec-hash.ts",
120
120
  "generate:spec-hash:check": "tsx scripts/generate-spec-hash.ts --check",
121
- "generate:api": "pnpm generate:api-types && pnpm generate:spec-hash && pnpm generate:openapi-routes",
121
+ "generate:api": "pnpm generate:api-types && pnpm generate:spec-hash && pnpm generate:openapi-routes && pnpm file-sizes:pin",
122
122
  "generate:api:check": "pnpm generate:api-types:check && pnpm generate:spec-hash:check && pnpm generate:openapi-routes:check",
123
123
  "generate:api-types": "openapi-typescript openapi/backend.yaml -o src/api/generated.ts",
124
124
  "generate:openapi-routes": "tsx scripts/generate-openapi-routes.ts",
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED — do not edit. Run pnpm generate:spec-hash to regenerate.
2
- export const SPEC_HASH = '387045558d18' as const;
3
- export const SPEC_HASH_FULL = '387045558d1867722a744ddb05cf5ebb7260c54df0ff272b868bd4dd2ad2a973' as const;
2
+ export const SPEC_HASH = '191634e28786' as const;
3
+ export const SPEC_HASH_FULL = '191634e28786b02448431968a7b8f6f3fbbcd5f5cadc9f48e3067511a790c38b' as const;
@@ -3778,6 +3778,23 @@ export interface paths {
3778
3778
  patch?: never;
3779
3779
  trace?: never;
3780
3780
  };
3781
+ "/api/comments/threads/{threadId}": {
3782
+ parameters: {
3783
+ query?: never;
3784
+ header?: never;
3785
+ path?: never;
3786
+ cookie?: never;
3787
+ };
3788
+ get?: never;
3789
+ put?: never;
3790
+ post?: never;
3791
+ /** Soft-delete a thread of either kind (creator or subject owner) */
3792
+ delete: operations["deleteCommentThread"];
3793
+ options?: never;
3794
+ head?: never;
3795
+ patch?: never;
3796
+ trace?: never;
3797
+ };
3781
3798
  "/api/comments/threads/{threadId}/accept-intent": {
3782
3799
  parameters: {
3783
3800
  query?: never;
@@ -3863,6 +3880,23 @@ export interface paths {
3863
3880
  patch?: never;
3864
3881
  trace?: never;
3865
3882
  };
3883
+ "/api/comments/threads/{threadId}/revert": {
3884
+ parameters: {
3885
+ query?: never;
3886
+ header?: never;
3887
+ path?: never;
3888
+ cookie?: never;
3889
+ };
3890
+ get?: never;
3891
+ put?: never;
3892
+ /** Revert a decided suggestion, restoring the passage an acceptance replaced */
3893
+ post: operations["revertSuggestion"];
3894
+ delete?: never;
3895
+ options?: never;
3896
+ head?: never;
3897
+ patch?: never;
3898
+ trace?: never;
3899
+ };
3866
3900
  }
3867
3901
  export type webhooks = Record<string, never>;
3868
3902
  export interface components {
@@ -7028,6 +7062,11 @@ export interface components {
7028
7062
  AnchorTraceRequest: {
7029
7063
  lastAnchoredOffset: number;
7030
7064
  };
7065
+ /** @description Acknowledges a soft-deleted thread: its id and the deletion stamp. */
7066
+ CommentThreadDeletedResponse: {
7067
+ threadId: string;
7068
+ deletedAt: string;
7069
+ };
7031
7070
  /** @description The acceptance lease: the opaque claim token that alone can complete the accept, and how long the lease lives. */
7032
7071
  SuggestionClaimResponse: {
7033
7072
  claimToken: string;
@@ -7045,6 +7084,14 @@ export interface components {
7045
7084
  claimToken: string;
7046
7085
  expectedRevision: string;
7047
7086
  };
7087
+ /** @description The reverted thread and the inverse document mutation that landed with it, or null when the suggestion had never been applied. */
7088
+ SuggestionRevertedResponse: {
7089
+ thread: components["schemas"]["CommentThreadSummary"];
7090
+ reverted: components["schemas"]["SuggestionApplied"] | null;
7091
+ };
7092
+ SuggestionRevertRequest: {
7093
+ expectedRevision?: string;
7094
+ };
7048
7095
  /** @description Server-enforced collaboration size limits, published so clients can pre-check. */
7049
7096
  CollabSyncLimits: {
7050
7097
  maxUpdateBytes: number;
@@ -7205,7 +7252,7 @@ export interface components {
7205
7252
  op: "insert" | "delete" | "replace";
7206
7253
  insertedText?: string;
7207
7254
  /** @enum {string} */
7208
- status: "open" | "accepted" | "rejected";
7255
+ status: "open" | "accepted" | "rejected" | "reverted";
7209
7256
  };
7210
7257
  /** @description One comment; body is null iff soft-deleted (redacted projection with empty mentions). */
7211
7258
  Comment: {
@@ -7246,6 +7293,8 @@ export interface components {
7246
7293
  canReopen: boolean;
7247
7294
  canDecideSuggestion: boolean;
7248
7295
  canWithdraw: boolean;
7296
+ canRevertSuggestion: boolean;
7297
+ canDeleteThread: boolean;
7249
7298
  };
7250
7299
  /** @description A comment thread without its comments. */
7251
7300
  CommentThreadSummary: {
@@ -13750,6 +13799,42 @@ export interface operations {
13750
13799
  };
13751
13800
  };
13752
13801
  };
13802
+ deleteCommentThread: {
13803
+ parameters: {
13804
+ query?: never;
13805
+ header?: never;
13806
+ path: {
13807
+ threadId: string;
13808
+ };
13809
+ cookie?: never;
13810
+ };
13811
+ requestBody?: never;
13812
+ responses: {
13813
+ /** @description Thread soft-deleted; the response acknowledges the id and the deletion stamp. Accepted suggestion text stays in the document */
13814
+ 200: {
13815
+ headers: {
13816
+ [name: string]: unknown;
13817
+ };
13818
+ content: {
13819
+ "application/json": components["schemas"]["CommentThreadDeletedResponse"];
13820
+ };
13821
+ };
13822
+ /** @description Thread not found, already deleted, or the caller is neither its creator nor a subject owner */
13823
+ 404: {
13824
+ headers: {
13825
+ [name: string]: unknown;
13826
+ };
13827
+ content?: never;
13828
+ };
13829
+ /** @description A live acceptance lease is held on the suggestion */
13830
+ 409: {
13831
+ headers: {
13832
+ [name: string]: unknown;
13833
+ };
13834
+ content?: never;
13835
+ };
13836
+ };
13837
+ };
13753
13838
  acceptSuggestionIntent: {
13754
13839
  parameters: {
13755
13840
  query?: never;
@@ -13963,4 +14048,51 @@ export interface operations {
13963
14048
  };
13964
14049
  };
13965
14050
  };
14051
+ revertSuggestion: {
14052
+ parameters: {
14053
+ query?: never;
14054
+ header?: never;
14055
+ path: {
14056
+ threadId: string;
14057
+ };
14058
+ cookie?: never;
14059
+ };
14060
+ requestBody: {
14061
+ content: {
14062
+ "application/json": components["schemas"]["SuggestionRevertRequest"];
14063
+ };
14064
+ };
14065
+ responses: {
14066
+ /** @description Suggestion reverted (terminal; thread stays resolved). On an accepted suggestion the inverse edit landed in the document on the SAME transaction and is reported; on a rejected one `reverted` is null */
14067
+ 200: {
14068
+ headers: {
14069
+ [name: string]: unknown;
14070
+ };
14071
+ content: {
14072
+ "application/json": components["schemas"]["SuggestionRevertedResponse"];
14073
+ };
14074
+ };
14075
+ /** @description Malformed expectedRevision, or an accepted suggestion reverted without one */
14076
+ 400: {
14077
+ headers: {
14078
+ [name: string]: unknown;
14079
+ };
14080
+ content?: never;
14081
+ };
14082
+ /** @description Thread not found, or the caller may not decide suggestions on it */
14083
+ 404: {
14084
+ headers: {
14085
+ [name: string]: unknown;
14086
+ };
14087
+ content?: never;
14088
+ };
14089
+ /** @description Terminal, never retry: not a suggestion thread, still open, already reverted, a claim is on the row, the document moved past expectedRevision, or the inserted text cannot be found or no longer reads as inserted — re-read the document and decide again */
14090
+ 409: {
14091
+ headers: {
14092
+ [name: string]: unknown;
14093
+ };
14094
+ content?: never;
14095
+ };
14096
+ };
14097
+ };
13966
14098
  }
@@ -86,6 +86,8 @@ function makeThread(over: Record<string, unknown> = {}) {
86
86
  canReopen: false,
87
87
  canDecideSuggestion: false,
88
88
  canWithdraw: false,
89
+ canRevertSuggestion: false,
90
+ canDeleteThread: false,
89
91
  },
90
92
  ...over,
91
93
  };
@@ -34,10 +34,36 @@ describe("the vocabulary tuples", () => {
34
34
  expect([...SUGGESTION_OPS]).toEqual(["insert", "delete", "replace"]);
35
35
  });
36
36
 
37
- it("SUGGESTION_STATUSES is exactly open, accepted and rejected", () => {
37
+ it("SUGGESTION_STATUSES is exactly open, accepted, rejected and reverted", () => {
38
38
  // The SECOND status level. The thread-level enum stays open|resolved —
39
39
  // `./schemas.test.ts` pins that separately — and the decision lives here.
40
- expect([...SUGGESTION_STATUSES]).toEqual(["open", "accepted", "rejected"]);
40
+ //
41
+ // PINNED AS A LIST, NOT A LENGTH, because widening it is the one change on
42
+ // this surface that cannot be walked back: a published `z.enum` refuses a
43
+ // member it has not shipped, and this one is nested inside the thread
44
+ // summary, so an unfamiliar status costs a client the whole document's
45
+ // comments. Failing here is the reminder that adding a member is a
46
+ // coordinated release — readers first, emitter second.
47
+ expect([...SUGGESTION_STATUSES]).toEqual([
48
+ "open",
49
+ "accepted",
50
+ "rejected",
51
+ "reverted",
52
+ ]);
53
+ });
54
+
55
+ it("accepts a reverted payload", () => {
56
+ // The state a taken-back decision lands in. Parsed here so the widening is
57
+ // proven at the schema, not just in the tuple above.
58
+ expect(
59
+ SuggestionSchema.safeParse(
60
+ makeSuggestion({
61
+ op: "replace",
62
+ insertedText: "x",
63
+ status: "reverted",
64
+ }),
65
+ ).success,
66
+ ).toBe(true);
41
67
  });
42
68
  });
43
69
 
@@ -355,6 +355,32 @@ export const ThreadCapabilitiesSchema = z.object({
355
355
  * their own suggestion.
356
356
  */
357
357
  canWithdraw: z.boolean(),
358
+ /**
359
+ * Take back a decision on a DECIDED suggestion — the complement of
360
+ * `canDecideSuggestion`, exactly as `canReopen` is the complement of
361
+ * `canResolve` one kind over.
362
+ *
363
+ * The edit band, like deciding: undoing an acceptance puts the replaced
364
+ * passage back into the document, which is the same authority as putting the
365
+ * proposal in. A reverted suggestion offers it no longer — `reverted` is
366
+ * terminal, so this is false there as well as on an open one.
367
+ */
368
+ canRevertSuggestion: z.boolean(),
369
+ /**
370
+ * Delete the whole thread — the conversation, not one comment in it.
371
+ *
372
+ * TRUE FOR BOTH KINDS, and it is the only capability here that does not
373
+ * mention one. A comment thread could previously only be dismantled by
374
+ * deleting its first comment, which cascaded; a suggestion thread could not be
375
+ * deleted at all, because its proposal is not a comment and there was nothing
376
+ * to aim the cascade at. One flag answers both.
377
+ *
378
+ * IT SAYS NOTHING ABOUT THE DOCUMENT. Deleting a thread whose suggestion was
379
+ * accepted leaves the accepted words where they are: they are the document's
380
+ * now, and taking them back is `canRevertSuggestion`'s act, deliberately kept
381
+ * separate so neither one silently performs the other.
382
+ */
383
+ canDeleteThread: z.boolean(),
358
384
  });
359
385
  export type ThreadCapabilities = z.infer<typeof ThreadCapabilitiesSchema>;
360
386
 
@@ -37,14 +37,43 @@ export const SUGGESTION_OPS = ["insert", "delete", "replace"] as const;
37
37
  export type SuggestionOp = (typeof SUGGESTION_OPS)[number];
38
38
 
39
39
  /**
40
- * open -> accepted | rejected. Terminal states are terminal: there is no
41
- * reopen for a decided suggestion (unlike comment resolve/reopen). This is
42
- * the SECOND status level — the thread-level `status` enum stays exactly
43
- * `open | resolved`, and a decided suggestion arrives as thread status
44
- * `resolved` with this field carrying which way it went. Widening the closed
45
- * thread enum instead would be a parse outage for every deployed client.
40
+ * `open -> accepted | rejected -> reverted`. This is the SECOND status level —
41
+ * the thread-level `status` enum stays exactly `open | resolved`, and a decided
42
+ * suggestion arrives as thread status `resolved` with this field carrying which
43
+ * way it went. Widening the closed thread enum instead would be a parse outage
44
+ * for every deployed client.
45
+ *
46
+ * A DECISION IS NO LONGER THE LAST WORD, BUT IT IS STILL ONE-WAY. This tuple
47
+ * read `open | accepted | rejected` with "terminal states are terminal: there is
48
+ * no reopen for a decided suggestion" written beside it, and that is now false:
49
+ * people are allowed to change their minds, and undoing an acceptance restores
50
+ * the passage the edit replaced. What has NOT changed is that the arrow never
51
+ * points back to `open`. `reverted` is its own terminal state rather than a
52
+ * return to the start, for two reasons — a reverted proposal is a decision
53
+ * somebody took and then took back, which is a different fact from one nobody
54
+ * has answered yet; and an `open` suggestion whose document still carries an
55
+ * application receipt is exactly the shape the server's reconciler treats as
56
+ * proof of an unacknowledged accept, so returning one to `open` would have it
57
+ * silently re-accepted by background maintenance.
58
+ *
59
+ * WIDENING THIS TUPLE IS ORDERED, NOT FREE. `SuggestionSchema` below is a
60
+ * `z.strictObject` nested inside `CommentThreadSummarySchema`, so for anyone who
61
+ * PARSES a thread list, one row carrying a status they do not know fails the
62
+ * whole list — every comment on that document, not just the odd suggestion — and
63
+ * it fails the first time somebody reverts rather than at deploy. No consumer in
64
+ * this workspace parses these at runtime today (the app is typed off the
65
+ * generated spec; the backend validates against its own local copies), which was
66
+ * checked rather than assumed, so this package leads the release and the emitter
67
+ * follows. The next person to add a member owes that check again for their own
68
+ * consumers — which is why `__tests__/suggestion.test.ts` pins this list
69
+ * exactly. See the ADR with slug `suggestion-revert-vocabulary`.
46
70
  */
47
- export const SUGGESTION_STATUSES = ["open", "accepted", "rejected"] as const;
71
+ export const SUGGESTION_STATUSES = [
72
+ "open",
73
+ "accepted",
74
+ "rejected",
75
+ "reverted",
76
+ ] as const;
48
77
  export type SuggestionStatus = (typeof SUGGESTION_STATUSES)[number];
49
78
 
50
79
  /**
@@ -24,6 +24,7 @@ export const openApiRoutes = {
24
24
  '/api/chats/{id}/pin': ['DELETE', 'POST'],
25
25
  '/api/comments': ['GET'],
26
26
  '/api/comments/threads': ['POST'],
27
+ '/api/comments/threads/{threadId}': ['DELETE'],
27
28
  '/api/comments/threads/{threadId}/accept-apply': ['POST'],
28
29
  '/api/comments/threads/{threadId}/accept-complete': ['POST'],
29
30
  '/api/comments/threads/{threadId}/accept-intent': ['POST'],
@@ -33,6 +34,7 @@ export const openApiRoutes = {
33
34
  '/api/comments/threads/{threadId}/reject': ['POST'],
34
35
  '/api/comments/threads/{threadId}/reopen': ['POST'],
35
36
  '/api/comments/threads/{threadId}/resolve': ['POST'],
37
+ '/api/comments/threads/{threadId}/revert': ['POST'],
36
38
  '/api/comments/{commentId}': ['DELETE', 'PATCH'],
37
39
  '/api/company-md/access-requests/{id}/approve': ['POST'],
38
40
  '/api/company-md/access-requests/{id}/deny': ['POST'],