@company-semantics/contracts 86.0.0 → 87.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.
@@ -29,7 +29,6 @@ Shared types for chat persistence, sharing, real-time events, and runtime profil
29
29
  - Chat mention offsets are ZERO-BASED UTF-16 CODE UNITS into the exact `content` string with `endOffset` EXCLUSIVE, and no Unicode normalization happens anywhere on the path — the same convention as `CommentMention`, because a mention means the same thing on both surfaces. An astral character counts as TWO units
30
30
  - Chat mention offsets are NULLABLE and null means "mentioned somewhere in this turn, highlight nothing", never an error. A user turn carries a real range; an assistant turn's mention is a typed part with no character range, and the nullability is what lets that land with no second schema
31
31
  - Identity on a chat mention renders from the mention ROW, never from the content characters under the range. A stale range can only mis-highlight; it can never misattribute
32
- - `SharedChatMessage` deliberately has NO mentions. The token-only share viewer is granted the CONTENT of a shared chat and nothing else; a mention row is identity, and identity is not in that grant
33
32
  - `ChatMentionableResponse` carries `MentionableCandidate` items imported from `comments`, not a chat-flavored copy. A candidate is a principal, a display name and an avatar on both surfaces — and on both, deliberately no email and no access level, because a picker that returned the band each candidate holds would be a way to enumerate who can do what
34
33
  - A chat message REQUIRES an `author`, and it is a `PrincipalRef` — so an assistant turn is the AGENT (`{ kind: "agent" }`), not a null and not a synthetic user id. `role` was sufficient while every chat had one human in it; with two participants `role: "user"` names a set, and a client with no author field can only render every human turn as the reader. That is silent misattribution, which is why this shipped in a MAJOR rather than as an optional field with a fallback (ADR-CONTRACTS-164)
35
34
  - `author` is a ref, never a display name. Identity renders through a read-time lookup — the same rule as a mention row's `displayName` — so a rename re-renders the whole transcript with no backfill
@@ -37,7 +36,8 @@ Shared types for chat persistence, sharing, real-time events, and runtime profil
37
36
  - `myRole` is per-READER, not a property of the chat: the same chat is `owner` to one person and `participant` to another over the same row shape. It is a hint for drawing; the server re-decides every owner-only action on its own
38
37
  - The summary carries a participant COUNT, never the roster. A 50-row sidebar page must not carry 50 rosters, and a chat's membership is not disclosed by the chat merely appearing in a list. `ChatDetail.participants` is where the names are, and it is optional — a missing roster is simply not drawn, whereas a guessed role is drawn wrong
39
38
  - A `ChatParticipant` is a USER, never a `PrincipalRef` — the deliberate asymmetry with a mention TARGET, which is a ref precisely so the agent can be named. The agent takes part in every chat by construction and has no participant row: it is mentionable, not a member, and cannot be added, removed or left
40
- - `ChatParticipantRole` has EXACTLY two members. A third would be an access band, and this is participation rather than an ACL: a participant sees the whole conversation, `owner` names the one person who additionally deletes, renames, pins and shares it, and a chat you can read half of is not a thing this surface models
39
+ - `ChatParticipantRole` is `owner` / `participant` (Collaborate) / `viewer` (Read only), and SHARING A CHAT IS ADDING A PARTICIPANT at one of the two grantable roles (`ChatGrantableRole`). Access is whole-chat or nothing — a viewer reads the same live conversation a participant does and writes nothing; there is no token, no snapshot and no public link (ADR-CONTRACTS-206)
40
+ - `ChatDetail.myRole` is REQUIRED, like the summary's: a chat opened from a link is on no loaded list page, and a guessed role draws a composer for a Read only viewer
41
41
  - The chat schemas are THREE files with a one-way load order: `enums.ts` (the closed vocabularies plus the `proactiveChatFields` / `multiPartyChatFields` groups) imports nothing from the domain, `event-schemas.ts` imports only from it, and `schemas.ts` imports from both and RE-EXPORTS them so `index.ts`, `types.ts` and the tests keep one import site. An import from either of the first two back into `schemas.ts` is a load cycle: the re-export edge is evaluated before `schemas.ts`'s body, so the shared declarations are reached in the temporal dead zone and every consumer throws a `ReferenceError` at import time. `continuity.ts` sits outside that set on the same terms: `schemas.ts` imports it, it never imports `schemas.ts`, and `index.ts` re-exports it directly (its types are declared beside its schemas, as `runtime-profile.ts` does)
42
42
  - `CHAT_PAGE_VIEWS` and `CHAT_PAGE_SCOPES` are a projection of the app's `Route` union (`company-semantics-app/src/platform/route-parser.ts`), not a parallel vocabulary. A view the parser can emit but the schema rejects fails the send outright rather than degrading
43
43
 
@@ -59,13 +59,14 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
59
59
 
60
60
  ## Public API
61
61
 
62
+ - `AddChatParticipantRequest` _(type)_ — Request for POST /api/chats/:chatId/participants — sharing the chat with one org member at a grantable role.
62
63
  - `BaseEvent` _(type)_ — Base envelope for all SSE events.
63
64
  - `BaseEventSchema`
64
65
  - `CHAT_CONTINUATION_REASONS` — Why a generation continues its predecessor.
65
66
  - `CHAT_DISCUSSION_REQUEST_TYPES` — The kinds of durable input request a turn can be scoped to.
66
67
  - `CHAT_PAGE_ENTITY_TYPES` — What kind of thing the reader has open, when they have one open.
67
68
  - `CHAT_PAGE_SCOPES` — The route parser's four URL scopes: unscoped, `/me/*`, `/team/*`, `/@:org/*`.
68
- - `CHAT_PAGE_VIEWS` — The app route parser's sixteen `type` values, projected onto the wire.
69
+ - `CHAT_PAGE_VIEWS` — The app route parser's fifteen `type` values, projected onto the wire.
69
70
  - `CHAT_RUNTIME_PROFILES` — Ordered list of available profiles for UI rendering.
70
71
  - `CONVERSATION_SUBJECT_KINDS` — The kinds of subject a chat can continue.
71
72
  - `ChatByInteractionResponse` _(type)_ — Response for GET /api/chats/by-interaction/:interactionId
@@ -86,6 +87,8 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
86
87
  - `ChatDiscussionScopeSchema` — WHAT THIS TURN IS ABOUT: one question of one input request, by stable id.
87
88
  - `ChatDomainEvent` _(type)_ — All domain events that carry full state.
88
89
  - `ChatDomainEventSchema`
90
+ - `ChatGrantableRole` _(type)_ — A role a share can grant: `participant` (Collaborate) or `viewer` (Read only).
91
+ - `ChatGrantableRoleSchema` — The roles a share can grant: Collaborate (`participant`) or Read only (`viewer`).
89
92
  - `ChatInvalidationEvent` _(type)_ — All invalidation events that signal staleness.
90
93
  - `ChatInvalidationEventSchema`
91
94
  - `ChatListFilters` _(type)_ — Filters for listing chats.
@@ -102,15 +105,13 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
102
105
  - `ChatPageContext` _(type)_ — Where the reader is, as identifiers — route, view, scope and an optional `{ type, id }` entity.
103
106
  - `ChatPageContextSchema` — Where the reader is, as identifiers.
104
107
  - `ChatParticipant` _(type)_ — One ACTIVE participant of a chat — a user id, a role, a read-time display name and when they joined.
105
- - `ChatParticipantRole` _(type)_ — What one person is to a chat: `owner` or `participant`.
108
+ - `ChatParticipantRole` _(type)_ — What one person is to a chat: `owner`, `participant` (Collaborate) or `viewer` (Read only).
106
109
  - `ChatParticipantRoleSchema` — What one person is to a chat.
107
110
  - `ChatParticipantSchema` — One active participant of a chat.
108
111
  - `ChatParticipantsResponse` _(type)_ — Response for GET /api/chats/:chatId/participants.
109
112
  - `ChatParticipantsResponseSchema` — Response for GET /api/chats/:chatId/participants.
110
113
  - `ChatRuntimeProfile` _(type)_ — Chat runtime profile — user-selectable orchestration strategy.
111
114
  - `ChatRuntimeProfileInfo` _(type)_ — Runtime profile metadata for UI rendering.
112
- - `ChatShareInfo` _(type)_ — Chat share record with snapshot metadata.
113
- - `ChatShareInfoSchema` — A chat share, as returned by the share APIs.
114
115
  - `ChatSseEvent` _(type)_ — SSE event types for chat updates.
115
116
  - `ChatSseEventSchema`
116
117
  - `ChatSuccessResponse` _(type)_ — Generic mutation success confirmation.
@@ -121,15 +122,10 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
121
122
  - `ChatSummarySchema`
122
123
  - `ChatUpdatedEvent` _(type)_ — Emitted when chat metadata changes (title, pin, etc.) Carries full current state - clients should replace…
123
124
  - `ChatUpdatedEventSchema`
124
- - `ChatVisibility` _(type)_ — Chat share visibility. - private: Token exists but access requires authentication as chat owner - public…
125
- - `ChatVisibilitySchema`
126
125
  - `ConversationSubjectKind` _(type)_
127
126
  - `ConversationSubjectKindSchema`
128
127
  - `CreateChatResponse` _(type)_ — Response for POST /api/chats
129
128
  - `CreateChatResponseSchema` — Response for POST /api/chats
130
- - `CreateShareRequest` _(type)_ — Request to create a new share for a chat.
131
- - `CreateShareResponse` _(type)_ — Response for POST /api/chats/:chatId/shares
132
- - `CreateShareResponseSchema` — Response for POST /api/chats/:chatId/shares
133
129
  - `DEFAULT_CHAT_RUNTIME_PROFILE` — Default profile when none is specified in the request.
134
130
  - `GetChatResponse` _(type)_ — Response for GET /api/chats/:id
135
131
  - `GetChatResponseSchema` — Response for GET /api/chats/:id
@@ -145,17 +141,11 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
145
141
  - `LaunchDispositionSchema`
146
142
  - `LaunchTargetSuperseded` _(type)_
147
143
  - `LaunchTargetSupersededSchema` — The 409 body the authoritative launch write returns when the posted chat is not — or can no longer become —…
148
- - `ListSharesResponse` _(type)_ — Response for GET /api/chats/:chatId/shares
149
- - `ListSharesResponseSchema` — Response for GET /api/chats/:chatId/shares
150
144
  - `ProactiveChatResolution` _(type)_ — Resolving an occurrence id to the CURRENT reader's chat — one of the four bounded states `ready` (with…
151
145
  - `ProactiveChatResolutionSchema` — Resolving an occurrence id to THIS reader's chat (INV-PROACTIVE-LINK).
152
146
  - `RecognizedProactiveKind` _(type)_ — A wire `proactiveKind` after recognition: a kind this contracts version knows, or the raw string it did not —…
153
147
  - `ResolveLaunchResponse` _(type)_
154
148
  - `ResolveLaunchResponseSchema` — Response of the read-only launch resolution.
155
- - `SharedChatMessage` _(type)_ — Individual message in a shared chat view.
156
- - `SharedChatMessageSchema`
157
- - `SharedChatView` _(type)_ — Shared chat view returned to viewers.
158
- - `SharedChatViewSchema`
159
149
  - `StartFreshChatResponse` _(type)_
160
150
  - `StartFreshChatResponseSchema` — Response of the start-fresh write: the NEW active generation of the subject, linked back to the chat it…
161
151
  - `TitleGenerationRequest` _(type)_ — Request to generate a title for a chat.
@@ -163,9 +153,7 @@ A chat can belong to a SUBJECT — the durable case a hand-off to chat is about
163
153
  - `TitleGenerationResponseSchema`
164
154
  - `TitleSource` _(type)_ — Title source - tracks how the title was set. - auto: Generated automatically by LLM - manual: Set by user…
165
155
  - `TitleSourceSchema`
166
- - `UpdateShareRequest` _(type)_ — Request to update an existing share.
167
- - `UpdateShareResponse` _(type)_ — Response for PATCH /api/shares/:shareId
168
- - `UpdateShareResponseSchema` — Response for PATCH /api/shares/:shareId
156
+ - `UpdateChatParticipantRequest` _(type)_ — Request for PATCH /api/chats/:chatId/participants/:userId — the owner moving someone between Read only and…
169
157
  - `recognizeProactiveKind` — Recognize a wire `proactiveKind`.
170
158
 
171
159
  <!-- END GENERATED: readme-public-api -->
@@ -32,6 +32,7 @@ const BASE_DETAIL = {
32
32
  id: "chat_1",
33
33
  title: "A long conversation",
34
34
  interactionId: "int_1",
35
+ myRole: "owner",
35
36
  createdAt: "2026-08-31T00:00:00.000Z",
36
37
  updatedAt: "2026-08-31T01:00:00.000Z",
37
38
  continuity: null,
@@ -67,6 +67,7 @@ const BASE_DETAIL = {
67
67
  id: "chat_1",
68
68
  title: "Candidate review",
69
69
  interactionId: "int_1",
70
+ myRole: "owner",
70
71
  createdAt: "2026-09-01T00:00:00.000Z",
71
72
  updatedAt: "2026-09-01T01:00:00.000Z",
72
73
  };
@@ -11,7 +11,6 @@ import {
11
11
  CHAT_DISCUSSION_REQUEST_TYPES,
12
12
  ChatDiscussionScopeSchema,
13
13
  ChatMessageSchema,
14
- SharedChatMessageSchema,
15
14
  } from "../schemas";
16
15
 
17
16
  const VALID = {
@@ -90,12 +89,6 @@ describe("where a scope may be SEEN", () => {
90
89
  expect("discussionScope" in ChatMessageSchema.shape).toBe(true);
91
90
  });
92
91
 
93
- it("is ABSENT from the shared-chat message, which is a separate type", () => {
94
- // A token-only share reader sees the turn without its anchor line, and the
95
- // question is named nowhere. This asymmetry is the design, not an oversight.
96
- expect("discussionScope" in SharedChatMessageSchema.shape).toBe(false);
97
- });
98
-
99
92
  it("is optional, so a turn about nothing in particular simply omits it", () => {
100
93
  const parsed = ChatMessageSchema.shape.discussionScope.safeParse(undefined);
101
94
  expect(parsed.success).toBe(true);
@@ -18,9 +18,6 @@
18
18
  * exclusive. An astral character counts as TWO. Asserted against a real slice
19
19
  * rather than stated, because a client measuring code POINTS produces ranges
20
20
  * that look right until an emoji precedes the mention.
21
- * - `SharedChatMessageSchema` did NOT gain mentions. The token-only share viewer
22
- * renders content, not identity chips, and a mention row is identity a
23
- * tokenholder was never granted.
24
21
  * - `ChatMentionableResponseSchema` carries `MentionableCandidateSchema` items —
25
22
  * the comments shape imported, not a chat-flavored copy of it.
26
23
  */
@@ -31,7 +28,6 @@ import {
31
28
  ChatMessageMentionSchema,
32
29
  ChatMessageSchema,
33
30
  GetChatResponseSchema,
34
- SharedChatMessageSchema,
35
31
  } from "../schemas.js";
36
32
 
37
33
  // ---------------------------------------------------------------------------
@@ -93,6 +89,7 @@ describe("ChatMessageSchema mentions", () => {
93
89
  id: "chat_1",
94
90
  title: "A conversation",
95
91
  interactionId: "int_1",
92
+ myRole: "owner",
96
93
  createdAt: "2026-09-02T00:00:00.000Z",
97
94
  updatedAt: "2026-09-02T01:00:00.000Z",
98
95
  continuity: null,
@@ -102,14 +99,6 @@ describe("ChatMessageSchema mentions", () => {
102
99
 
103
100
  expect(parsed.messages[0].mentions).toHaveLength(1);
104
101
  });
105
-
106
- it("the share viewer's message shape did not gain mentions", () => {
107
- // A token holder is granted the CONTENT of a shared chat and nothing else.
108
- // Mention rows are identity, and identity is not in the grant.
109
- expect(Object.keys(SharedChatMessageSchema.shape)).not.toContain(
110
- "mentions",
111
- );
112
- });
113
102
  });
114
103
 
115
104
  describe("ChatMessageMentionSchema", () => {
@@ -31,6 +31,7 @@ import {
31
31
  ChatDetailSchema,
32
32
  ChatMessageSchema,
33
33
  ChatParticipantRoleSchema,
34
+ ChatGrantableRoleSchema,
34
35
  ChatParticipantSchema,
35
36
  ChatParticipantsResponseSchema,
36
37
  ChatSummaryExtendedSchema,
@@ -122,6 +123,7 @@ const BASE_DETAIL = {
122
123
  id: "chat_1",
123
124
  title: "Q3 planning",
124
125
  interactionId: "int_1",
126
+ myRole: "owner",
125
127
  createdAt: ISO,
126
128
  updatedAt: ISO,
127
129
  continuity: null,
@@ -176,14 +178,21 @@ describe("ChatMessageSchema author", () => {
176
178
  });
177
179
 
178
180
  describe("the participant role vocabulary", () => {
179
- it("has exactly two members — participation, not an access band", () => {
180
- // A third role would be a band ladder, and a band ladder is the ACL entity
181
- // this decision declined. A participant sees the whole conversation.
182
- expect(ChatParticipantRoleSchema.options).toEqual(["owner", "participant"]);
181
+ it("is owner, participant (Collaborate) and viewer (Read only) — ADR-CONTRACTS-206", () => {
182
+ expect(ChatParticipantRoleSchema.options).toEqual([
183
+ "owner",
184
+ "participant",
185
+ "viewer",
186
+ ]);
183
187
  });
184
188
 
185
- it("rejects a band that reads like an access level", () => {
186
- for (const role of ["viewer", "commenter", "editor", "admin"]) {
189
+ it("grants only Collaborate or Read only — owner is never granted", () => {
190
+ expect(ChatGrantableRoleSchema.options).toEqual(["participant", "viewer"]);
191
+ expect(ChatGrantableRoleSchema.safeParse("owner").success).toBe(false);
192
+ });
193
+
194
+ it("rejects a band that is not whole-chat access", () => {
195
+ for (const role of ["commenter", "editor", "admin"]) {
187
196
  expect(ChatParticipantRoleSchema.safeParse(role).success, role).toBe(
188
197
  false,
189
198
  );
@@ -211,9 +211,9 @@ describe("ChatPageContext is the ceiling on both axes", () => {
211
211
  expect(ChatPageContextSchema.safeParse(VALID).success).toBe(true);
212
212
  expect(
213
213
  ChatPageContextSchema.safeParse({
214
- route: "/chat/abc",
215
- view: "chat",
216
- scope: "none",
214
+ route: "/me/work",
215
+ view: "work",
216
+ scope: "me",
217
217
  entity: { type: "chat", id: "abc" },
218
218
  }).success,
219
219
  ).toBe(true);
package/src/chat/enums.ts CHANGED
@@ -20,7 +20,6 @@
20
20
 
21
21
  import { z } from "zod";
22
22
 
23
- export const ChatVisibilitySchema = z.enum(["private", "public"]);
24
23
  export const TitleSourceSchema = z.enum(["auto", "manual"]);
25
24
  export const InvalidationReasonSchema = z.enum([
26
25
  "external-mutation",
@@ -44,18 +43,32 @@ export const ChatOriginSchema = z.enum(["user", "proactive"]);
44
43
  /**
45
44
  * What one person is to a chat.
46
45
  *
47
- * TWO ROLES AND NO BAND LADDER. This is participation, not an access-control
48
- * list: a participant sees the whole conversation and may add someone else,
49
- * and `owner` names the one person who additionally deletes, renames, pins and
50
- * shares it. There is deliberately no `viewer`, no `commenter` and no
51
- * per-participant scope — a chat you can read half of is not a thing this
52
- * surface models, and a third role is what an ACL entity would have given us
53
- * along with the grant compiler this decision declines.
46
+ * Participation with ONE access distinction, whole-chat or nothing:
47
+ * - `participant` (Collaborate) sees the whole live conversation, takes turns
48
+ * under the turn lock and may share the chat onward;
49
+ * - `viewer` (Read only) sees the same whole live conversation and writes
50
+ * nothing — no turn, no share, no retitle;
51
+ * - `owner` is the one participant who additionally deletes, renames, pins and
52
+ * changes another person's access.
53
+ *
54
+ * Sharing a chat IS adding a participant row with one of the two grantable
55
+ * roles (ADR-CONTRACTS-206). There is still no per-participant scope — a chat
56
+ * you can read half of is not a thing this surface models.
54
57
  *
55
58
  * EXACTLY ONE OWNER, and it never changes hands: ownership transfer is out of
56
59
  * scope, and the owner cannot leave their own chat.
57
60
  */
58
- export const ChatParticipantRoleSchema = z.enum(["owner", "participant"]);
61
+ export const ChatParticipantRoleSchema = z.enum([
62
+ "owner",
63
+ "participant",
64
+ "viewer",
65
+ ]);
66
+
67
+ /**
68
+ * The roles a share can grant: Collaborate (`participant`) or Read only
69
+ * (`viewer`). `owner` is never granted — it is the chat's creator, once.
70
+ */
71
+ export const ChatGrantableRoleSchema = z.enum(["participant", "viewer"]);
59
72
 
60
73
  /**
61
74
  * The three proactive wire fields, stated ONCE and spread into every shape
@@ -134,8 +147,8 @@ export const multiPartyChatFields = {
134
147
  * the same chat is `owner` to one person and `participant` to another, and
135
148
  * the same row shape reaches both.
136
149
  *
137
- * The client gates the owner-only affordances (delete, rename, pin, share) on
138
- * this. It is a hint for drawing, not the enforcement — the server re-decides
150
+ * The client gates the owner-only affordances (delete, rename, pin, access
151
+ * changes) and the read-only composer on this. It is a hint for drawing, not the enforcement — the server re-decides
139
152
  * every one of them on its own.
140
153
  */
141
154
  myRole: ChatParticipantRoleSchema,
package/src/chat/index.ts CHANGED
@@ -10,13 +10,7 @@
10
10
  // =============================================================================
11
11
 
12
12
  export type {
13
- ChatVisibility,
14
13
  ChatSummary,
15
- ChatShareInfo,
16
- SharedChatView,
17
- SharedChatMessage,
18
- CreateShareRequest,
19
- UpdateShareRequest,
20
14
  // Chat lifecycle types
21
15
  TitleSource,
22
16
  ChatListFilters,
@@ -46,9 +40,6 @@ export type {
46
40
  ChatByInteractionResponse,
47
41
  ChatListResponse,
48
42
  CreateChatResponse,
49
- CreateShareResponse,
50
- ListSharesResponse,
51
- UpdateShareResponse,
52
43
  ChatMessage,
53
44
  // Mentions on a chat turn (ADR-CONTRACTS-160): a PrincipalRef target, so the
54
45
  // agent is a legal mention target on the chat surface too.
@@ -57,6 +48,9 @@ export type {
57
48
  // Participants (ADR-CONTRACTS-164): a chat is a room with a membership, not
58
49
  // an ACL entity. The agent is mentionable but never a member.
59
50
  ChatParticipantRole,
51
+ ChatGrantableRole,
52
+ AddChatParticipantRequest,
53
+ UpdateChatParticipantRequest,
60
54
  ChatParticipant,
61
55
  ChatParticipantsResponse,
62
56
  ChatDetail,
@@ -71,7 +65,6 @@ export type {
71
65
  // =============================================================================
72
66
 
73
67
  export {
74
- ChatVisibilitySchema,
75
68
  TitleSourceSchema,
76
69
  InvalidationReasonSchema,
77
70
  ChatChangedFieldSchema,
@@ -80,9 +73,6 @@ export {
80
73
  ChatSummaryExtendedSchema,
81
74
  ProactiveChatResolutionSchema,
82
75
  TitleGenerationResponseSchema,
83
- SharedChatMessageSchema,
84
- ChatShareInfoSchema,
85
- SharedChatViewSchema,
86
76
  BaseEventSchema,
87
77
  ChatCreatedEventSchema,
88
78
  ChatUpdatedEventSchema,
@@ -97,15 +87,13 @@ export {
97
87
  ChatByInteractionResponseSchema,
98
88
  ChatListResponseSchema,
99
89
  CreateChatResponseSchema,
100
- CreateShareResponseSchema,
101
- ListSharesResponseSchema,
102
- UpdateShareResponseSchema,
103
90
  ChatMessageSchema,
104
91
  ChatMessageMentionSchema,
105
92
  ChatMentionableResponseSchema,
106
93
  // Participants: the role vocabulary, one participant row, and the list
107
94
  // envelope for GET /api/chats/:chatId/participants.
108
95
  ChatParticipantRoleSchema,
96
+ ChatGrantableRoleSchema,
109
97
  ChatParticipantSchema,
110
98
  ChatParticipantsResponseSchema,
111
99
  ChatDetailSchema,
@@ -21,7 +21,6 @@ import { PrincipalRefSchema } from "../identity/agent";
21
21
  import { ChatContinuitySchema } from "./continuity";
22
22
  import {
23
23
  ChatParticipantRoleSchema,
24
- ChatVisibilitySchema,
25
24
  multiPartyChatFields,
26
25
  proactiveChatFields,
27
26
  TitleSourceSchema,
@@ -30,12 +29,12 @@ import {
30
29
  // The chat vocabulary and the SSE event contract, re-exported so this module
31
30
  // stays the single import site for every chat schema.
32
31
  export {
33
- ChatVisibilitySchema,
34
32
  TitleSourceSchema,
35
33
  InvalidationReasonSchema,
36
34
  ChatChangedFieldSchema,
37
35
  ChatOriginSchema,
38
36
  ChatParticipantRoleSchema,
37
+ ChatGrantableRoleSchema,
39
38
  } from "./enums";
40
39
 
41
40
  export {
@@ -111,53 +110,6 @@ export const TitleGenerationResponseSchema = z.object({
111
110
  isAutoGenerated: z.boolean(),
112
111
  });
113
112
 
114
- // =============================================================================
115
- // Share Types
116
- // =============================================================================
117
-
118
- export const SharedChatMessageSchema = z.object({
119
- role: z.enum(["user", "assistant"]),
120
- content: z.string(),
121
- parts: z.array(z.unknown()).optional(),
122
- });
123
-
124
- /**
125
- * A chat share, as returned by the share APIs.
126
- *
127
- * SHOW-ONCE (ADR-BE-460). `token` and `shareUrl` are present ONLY on the
128
- * response that creates the share. Share tokens are stored as a digest, so the
129
- * server cannot redisplay one afterwards — listing or updating a share returns
130
- * the record without them, and re-obtaining a link means revoke-and-reshare.
131
- *
132
- * This is the industry norm for capability links, and it is what removes the
133
- * last reason to keep a recoverable copy of the token: a share list that can
134
- * still show you the URL is a share list that can leak every live link at once.
135
- * Consumers must treat both fields as optional and drive their copy-link
136
- * affordance off creation, not off a fetched share.
137
- */
138
- export const ChatShareInfoSchema = z.object({
139
- id: z.string(),
140
- chatId: z.string(),
141
- /** Create-time only — never returned by list or update (show-once). */
142
- token: z.string().optional(),
143
- visibility: ChatVisibilitySchema,
144
- /** Create-time only — never returned by list or update (show-once). */
145
- shareUrl: z.string().optional(),
146
- messageCountAtShare: z.number().int(),
147
- titleAtShare: z.string(),
148
- createdAt: IsoDateTime,
149
- createdByName: z.string(),
150
- isRevoked: z.boolean(),
151
- });
152
-
153
- export const SharedChatViewSchema = z.object({
154
- title: z.string(),
155
- messages: z.array(SharedChatMessageSchema),
156
- sharedAt: IsoDateTime,
157
- sharedByName: z.string(),
158
- visibility: ChatVisibilitySchema,
159
- });
160
-
161
113
  // =============================================================================
162
114
  // Message Types
163
115
  // =============================================================================
@@ -331,10 +283,6 @@ export const ChatMessageSchema = z.object({
331
283
  * not know this field renders the transcript exactly as it did before — the
332
284
  * same reasoning as `compactedThroughSequence`.
333
285
  *
334
- * ON THE AUTHENTICATED TRANSCRIPT ONLY. `SharedChatMessageSchema` is a
335
- * separate type and does not carry it, so a scoped turn shown to a token-only
336
- * share reader has no anchor line and names no question. That asymmetry is
337
- * the point, not an oversight.
338
286
  */
339
287
  discussionScope: ChatDiscussionScopeSchema.optional(),
340
288
  });
@@ -398,22 +346,6 @@ export const CreateChatResponseSchema = z.object({
398
346
  chat: ChatSummaryExtendedSchema,
399
347
  });
400
348
 
401
- /** Response for POST /api/chats/:chatId/shares */
402
- export const CreateShareResponseSchema = z.object({
403
- share: ChatShareInfoSchema,
404
- shareUrl: z.string(),
405
- });
406
-
407
- /** Response for GET /api/chats/:chatId/shares */
408
- export const ListSharesResponseSchema = z.object({
409
- shares: z.array(ChatShareInfoSchema),
410
- });
411
-
412
- /** Response for PATCH /api/shares/:shareId */
413
- export const UpdateShareResponseSchema = z.object({
414
- share: ChatShareInfoSchema,
415
- });
416
-
417
349
  /**
418
350
  * Chat detail object in GET /api/chats/:id response.
419
351
  * Extends the base summary with interactionId (needed for append-on-edit flows).
@@ -469,6 +401,13 @@ export const ChatDetailSchema = z.object({
469
401
  * an empty array is not a state the server produces.
470
402
  */
471
403
  participants: z.array(ChatParticipantSchema).optional(),
404
+ /**
405
+ * What the READER is to this chat — the same per-reader fact the summary
406
+ * carries as `myRole`, on the detail too so a chat opened from a link (and
407
+ * so on no loaded list page) still knows whether to draw a composer.
408
+ * REQUIRED: guessing it draws a composer for a Read only viewer.
409
+ */
410
+ myRole: ChatParticipantRoleSchema,
472
411
  /**
473
412
  * The chat's place in its subject's lineage (chat-continuity-wire-vocabulary).
474
413
  * NULL for a chat that belongs to no subject. REQUIRED, not optional: a
@@ -509,7 +448,7 @@ export const ChatMentionableResponseSchema = z.object({
509
448
  // =============================================================================
510
449
 
511
450
  /**
512
- * The app route parser's sixteen `type` values, projected onto the wire.
451
+ * The app route parser's fifteen `type` values, projected onto the wire.
513
452
  *
514
453
  * A PROJECTION, not a parallel vocabulary: keep it in step with
515
454
  * `company-semantics-app/src/platform/route-parser.ts`. A view the parser can
@@ -518,7 +457,6 @@ export const ChatMentionableResponseSchema = z.object({
518
457
  * reads that parser's source and compares the two.
519
458
  */
520
459
  export const CHAT_PAGE_VIEWS = [
521
- "chat",
522
460
  "company-md",
523
461
  "execution-detail",
524
462
  "internal-admin",