@company-semantics/contracts 100.0.0 → 101.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.
@@ -1,26 +1,16 @@
1
1
  /**
2
2
  * Org/Workspace Response Schemas
3
3
  *
4
- * Zod response schemas for workspace core endpoints.
5
- * Canonical location for runtime validation of workspace API responses.
6
- *
7
- * Covers (defined here):
8
- * - workspace.ts (GET /api/workspace, /access, /members)
9
- * - members.ts (DELETE/PATCH /api/workspace/members)
10
- * - user-orgs.ts (GET /api/user/orgs, POST /api/user/active-org, /leave)
11
- * - scope-check.ts (GET /api/scope/check, POST /api/scope/check-batch)
12
- * - capabilities (GET /api/capabilities)
13
- *
14
- * The rest of the org schema corpus lives in sibling `*-schemas.ts` modules
15
- * and is re-exported below, so `./schemas` stays the one import path for
16
- * every org schema.
4
+ * The one import path for every org schema. The corpus lives in sibling
5
+ * `*-schemas.ts` modules, each re-exported whole below:
6
+ * - workspace-member-schemas.ts (GET/DELETE/PATCH /api/workspace/members)
7
+ * - workspace-overview-schemas.ts (GET /api/workspace, /access)
8
+ * - reporting-write-schemas.ts (PUT /api/reporting/positions/:id/manager,
9
+ * POST /api/chat/interactive-tasks/submit)
10
+ * - user-org-schemas.ts (GET /api/user/orgs, POST /api/user/active-org,
11
+ * /leave, GET/POST /api/scope/check*)
12
+ * - capability-schemas.ts (GET /api/capabilities)
17
13
  */
18
- import { z } from "zod";
19
- import { CursorPageSchema } from "../api/primitives";
20
- import { AI_PAUSE_REASONS } from "../billing/vocabulary";
21
- import { OrgChartRoleSchema } from "../permissions/orgchart-roles";
22
- import { PositionRefSchema } from "../identity/position-ref";
23
- import { AuthorityMutabilitySchema } from "./authority-schemas";
24
14
 
25
15
  // Sibling schema modules — re-exported whole; ./schemas is their only import path.
26
16
  export * from "./workspace-admin-schemas";
@@ -29,532 +19,8 @@ export * from "./org-governance-schemas";
29
19
  export * from "./org-unit-schemas";
30
20
  export * from "./authority-schemas";
31
21
  export * from "./org-unit-api-schemas";
32
-
33
- // ---------------------------------------------------------------------------
34
- // Sub-schemas
35
- // ---------------------------------------------------------------------------
36
-
37
- const WorkspaceMemberUnitSummarySchema = z.object({
38
- unitId: z.string(),
39
- unitName: z.string(),
40
- unitPath: z.string(),
41
- role: z.enum(["owner", "manager", "member"]),
42
- });
43
-
44
- /**
45
- * Designation a member holds over a unit they manage (ADR-CTRL-112).
46
- *
47
- * Precedence, highest-first: whole_org > admin > unit_owner > delegate > member.
48
- * The org-wide designations (`whole_org`, `admin`) anchor to the org root
49
- * unit. This vocabulary is DISPLAY-ONLY — it summarizes management reach for
50
- * UI surfaces and is NEVER an authorization input (authority resolution uses
51
- * the orthogonal membership/delegation axes, not this enum).
52
- */
53
- export const OrgUnitDesignationSchema = z.enum([
54
- "whole_org",
55
- "admin",
56
- "unit_owner",
57
- "delegate",
58
- "member",
59
- ]);
60
- export type OrgUnitDesignation = z.infer<typeof OrgUnitDesignationSchema>;
61
-
62
- export const WorkspaceMemberUnitDesignationEntrySchema = z.object({
63
- unitId: z.string(),
64
- unitName: z.string(),
65
- designation: OrgUnitDesignationSchema,
66
- /**
67
- * Whether this designation is REMOVABLE in place (`user_managed` — an explicit
68
- * overlay grant or `l{N}_unit_owner` membership) or DERIVED from reporting
69
- * topology (`derived` — the senior person homed in a unit with no manager
70
- * inside it, ADR-BE-267/294). A `derived` owner can't be revoked: the holder
71
- * must relinquish it (recorded as a higher-provenance suppression that the
72
- * structural projection honors) or the upstream chart must change. Display +
73
- * affordance gating only; never an authorization input.
74
- */
75
- mutability: AuthorityMutabilitySchema,
76
- });
77
- export type WorkspaceMemberUnitDesignationEntry = z.infer<
78
- typeof WorkspaceMemberUnitDesignationEntrySchema
79
- >;
80
-
81
- const WorkspaceMemberSchema = z.object({
82
- id: z.string(),
83
- name: z.string(),
84
- email: z.string(),
85
- /**
86
- * Resolved avatar photo URL for the member, or `null`/absent when none is
87
- * stored. SOURCE-AGNOSTIC: today the backend resolves this from the Slack
88
- * profile photo cached on the user record, but the field name deliberately
89
- * does not encode the origin so future sources (in-app upload, etc.) slot in
90
- * behind the backend avatar resolver without a contract change. UI MUST fall
91
- * back to initials when this is absent. Optional because it is an additive
92
- * display-only enrichment (see ADR-CONTRACTS-055).
93
- */
94
- avatarUrl: z.string().nullable().optional(),
95
- /** The occupant's Position seat title (org-scoped). Null when the seat is untitled. */
96
- jobTitle: z.string().nullable(),
97
- /**
98
- * The member's Position seat in the org graph — the id every reporting write
99
- * is keyed on (ADR-CONTRACTS-131). Present here so a user-ADDRESSED surface
100
- * (the members table, a person picker) can resolve a seat by READ and hand
101
- * the position straight to `PUT /api/reporting/positions/{positionId}/manager`,
102
- * instead of a write path minting one with `resolveSeat(create: true)`.
103
- *
104
- * Nullable: a member with no materialized seat is a legitimate state, and a
105
- * read must report that honestly rather than force the server to create one.
106
- */
107
- seatPositionId: z.string().uuid().nullable(),
108
- /**
109
- * Display role — the member's highest org-chart standing, or `null` for a
110
- * plain member with no org-chart authority. Render via `orgChartRoleLabel`.
111
- */
112
- role: OrgChartRoleSchema.nullable(),
113
- /** Raw RBAC role names (e.g. 'owner', 'admin', 'delegate'). Superset of `role`. */
114
- roleNames: z.array(z.string()),
115
- joinedAt: z.string(),
116
- /** ISO timestamp of last activity; null if never recorded. */
117
- lastActiveAt: z.string().nullable(),
118
- /**
119
- * Home org-unit (`users.primary_unit_id`). Null for org-scoped users
120
- * (no level-2 home assignment). Used by the unit-detail Members table to
121
- * roll up "people in this org-unit" across the subtree.
122
- */
123
- primaryUnitId: z.string().uuid().nullable(),
124
- /** OrgUnit memberships for this member, capped at a small number server-side. */
125
- unitMemberships: z.array(WorkspaceMemberUnitSummarySchema),
126
- /**
127
- * Per-unit org-chart designations the member holds (ADR-CTRL-112). Carries
128
- * BOTH management-reach entries (whole_org/admin/unit_owner/delegate) and
129
- * plain contributor entries (`member`); consumers split by designation.
130
- * Display-only summary; never an authorization input. Empty array when the
131
- * member holds no per-unit designation.
132
- */
133
- unitDesignations: z.array(WorkspaceMemberUnitDesignationEntrySchema),
134
- /** True when server truncated `unitMemberships`; detail endpoint returns the full list. */
135
- unitMembershipsTruncated: z.boolean(),
136
- /** Invite lifecycle status. `null` for members without an invite record. */
137
- inviteStatus: z.enum(["active", "pending", "expired"]).nullable(),
138
- });
139
-
140
- const MemberRecentActionSchema = z.object({
141
- id: z.string(),
142
- timestamp: z.string(),
143
- action: z.string(),
144
- summary: z.string(),
145
- });
146
-
147
- /**
148
- * Which bundle or role contributed a resolved scope (ADR-CTRL-360).
149
- *
150
- * `member_default` / `admin` / `account_owner` are the static actor-class
151
- * bundles AUTH-011C re-homed the deleted role scope lists onto (ADR-BE-184);
152
- * `role:<name>` is a surviving RBAC role assignment. Open string rather than an
153
- * enum because `rbac_roles` is data, not a closed vocabulary.
154
- *
155
- * A scope carries EVERY band that granted it — composition is a union, not a
156
- * ladder, so an owner who also holds admin authority legitimately shows both.
157
- * Display/audit only; `effectiveScopes` remains the authorization surface.
158
- */
159
- const ScopeProvenanceEntrySchema = z.object({
160
- scope: z.string(),
161
- bands: z.array(z.string()).min(1),
162
- });
163
-
164
- export const WorkspaceMemberDetailSchema = WorkspaceMemberSchema.extend({
165
- effectiveScopes: z.array(z.string()),
166
- /**
167
- * Per-scope provenance for {@link WorkspaceMemberDetailSchema.shape.effectiveScopes}.
168
- * Optional: absence means the server did not compute it (legacy callsite, or a
169
- * non-user actor), never that the scopes were granted by nothing.
170
- */
171
- scopeProvenance: z.array(ScopeProvenanceEntrySchema).optional(),
172
- recentActions: z.array(MemberRecentActionSchema),
173
- });
174
-
175
- /**
176
- * Workspace member for the members list. Human users only (no agent actors).
177
- * Canonical type — inferred from {@link WorkspaceMemberSchema} so the runtime
178
- * schema is the single source of truth (no parallel hand-written interface).
179
- */
180
- export type WorkspaceMember = z.infer<typeof WorkspaceMemberSchema>;
181
-
182
- /**
183
- * Full member detail (GET /api/workspace/members/:userId and /api/me self-view).
184
- * Inferred from {@link WorkspaceMemberDetailSchema}.
185
- */
186
- export type WorkspaceMemberDetail = z.infer<typeof WorkspaceMemberDetailSchema>;
187
-
188
- export const RoleCatalogEntrySchema = z.object({
189
- name: z.string(),
190
- type: z.enum(["system", "custom"]),
191
- description: z.string(),
192
- scopes: z.array(z.string()),
193
- memberCount: z.number().int().nonnegative(),
194
- });
195
-
196
- export const RoleCatalogResponseSchema = z.object({
197
- roles: z.array(RoleCatalogEntrySchema),
198
- });
199
-
200
- const WorkspaceOwnerSchema = z.object({
201
- id: z.string(),
202
- name: z.string(),
203
- email: z.string(),
204
- });
205
-
206
- // ---------------------------------------------------------------------------
207
- // GET /api/workspace/access
208
- // ---------------------------------------------------------------------------
209
-
210
- export const WorkspaceAccessResponseSchema = z.object({
211
- hasAccess: z.boolean(),
212
- });
213
-
214
- export type WorkspaceAccessResponse = z.infer<
215
- typeof WorkspaceAccessResponseSchema
216
- >;
217
-
218
- // ---------------------------------------------------------------------------
219
- // GET /api/workspace
220
- // PATCH /api/workspace/name (returns WorkspaceOverview)
221
- // ---------------------------------------------------------------------------
222
-
223
- /**
224
- * Icon options for the `owner` org-chart role (ADR-CONT-070). Selected
225
- * alongside the owner title in the Organization structure editor; `crown` is
226
- * the default. Presentation only — never an authorization input.
227
- */
228
- export const OrgOwnerIconSchema = z.enum([
229
- "crown",
230
- "crown-cross",
231
- "person-simple-run",
232
- "person",
233
- "medal-military",
234
- "dog",
235
- "cat",
236
- "steering-wheel",
237
- ]);
238
-
239
- /**
240
- * An org's stored marks. See `OrgLogos` for what each slot means and
241
- * `resolveOrgMark` for the order the square tile prefers them in.
242
- */
243
- export const OrgLogosSchema = z.object({
244
- appLogoUrl: z.string().nullable(),
245
- logoUrl: z.string().nullable(),
246
- });
247
-
248
- export type OrgLogosShape = z.infer<typeof OrgLogosSchema>;
249
-
250
- export const WorkspaceOverviewSchema = z.object({
251
- id: z.string(),
252
- name: z.string(),
253
- slug: z.string(),
254
- type: z.enum(["personal", "shared"]),
255
- /**
256
- * The org's two marks. See `OrgLogos` for what each slot means and
257
- * `resolveOrgMark` for the order the square tile prefers them in.
258
- */
259
- logos: OrgLogosSchema,
260
- owner: WorkspaceOwnerSchema,
261
- /**
262
- * Org-configured display title for the `owner` org-chart role
263
- * (ADR-CONTRACTS-061). Server-resolved: `orgs.owner_title` when set,
264
- * otherwise the default "CEO". Always present; pass to
265
- * `orgChartRoleLabel(role, { ownerTitle })` wherever the owner badge or
266
- * section heading renders. Presentation only — never an authorization input.
267
- */
268
- ownerTitle: z.string(),
269
- /**
270
- * Org-configured icon for the `owner` org-chart role (ADR-CONT-070).
271
- * Server-resolved: `orgs.owner_icon` when set, otherwise the default
272
- * "crown". Always present. Presentation only — never an authorization input.
273
- */
274
- ownerIcon: OrgOwnerIconSchema,
275
- createdAt: z.string(),
276
- memberCount: z.number(),
277
- claimable: z.boolean(),
278
- });
279
-
280
- export type OrgOwnerIcon = z.infer<typeof OrgOwnerIconSchema>;
281
-
282
- export type WorkspaceOverview = z.infer<typeof WorkspaceOverviewSchema>;
283
-
284
- // ---------------------------------------------------------------------------
285
- // GET /api/workspace/members
286
- // ---------------------------------------------------------------------------
287
-
288
- export const WorkspaceMembersResponseSchema = CursorPageSchema(
289
- WorkspaceMemberSchema,
290
- );
291
-
292
- export type WorkspaceMembersResponse = z.infer<
293
- typeof WorkspaceMembersResponseSchema
294
- >;
295
-
296
- // ---------------------------------------------------------------------------
297
- // DELETE /api/workspace/members/:id
298
- // ---------------------------------------------------------------------------
299
-
300
- export const RemoveMemberResponseSchema = z.object({
301
- success: z.boolean(),
302
- memberId: z.string(),
303
- memberEmail: z.string(),
304
- message: z.string(),
305
- });
306
-
307
- export type RemoveMemberResponse = z.infer<typeof RemoveMemberResponseSchema>;
308
-
309
- // ---------------------------------------------------------------------------
310
- // PATCH /api/workspace/members/:id/role
311
- // ---------------------------------------------------------------------------
312
-
313
- export const ChangeMemberRoleResponseSchema = z.object({
314
- success: z.boolean(),
315
- memberId: z.string(),
316
- memberEmail: z.string(),
317
- previousRole: z.string(),
318
- newRole: z.string(),
319
- message: z.string(),
320
- });
321
-
322
- export type ChangeMemberRoleResponse = z.infer<
323
- typeof ChangeMemberRoleResponseSchema
324
- >;
325
-
326
- // ---------------------------------------------------------------------------
327
- // PUT /api/reporting/positions/:positionId/manager
328
- // ---------------------------------------------------------------------------
329
-
330
- /**
331
- * Response for the one idempotent set-or-clear of a seat's solid-line manager —
332
- * the reporting edge the org chart is built from. Position-keyed at BOTH ends
333
- * (ADR-CONTRACTS-131): `reportPositionId` is the seat whose edge moved and
334
- * `manager` is the POSITION it now reports to, null after a clear. A position
335
- * names a vacant seat and an accountless occupant's seat just as well as a
336
- * filled one, which the retired member-or-open-role union could not.
337
- *
338
- * `relationshipType` is a literal rather than the identity-domain
339
- * `ReportingRelationshipTypeSchema`: importing that here would invert the
340
- * existing people-org-chart → org/schemas dependency and recreate the
341
- * module-init cycle. This route only ever writes solid edges, so the narrower
342
- * literal is also the truthful shape. (`PositionReportingRelationshipTypeSchema`
343
- * in ./position-reporting carries the open {solid, dotted} vocabulary for the
344
- * read model.)
345
- *
346
- * Distinct from team membership (RBAC scopes) and `primary_unit_id` (home
347
- * unit): neither of those places a person in the reporting hierarchy.
348
- * See ADR-BE-253.
349
- */
350
- export const SetSeatManagerResponseSchema = z.object({
351
- success: z.boolean(),
352
- reportPositionId: z.string().uuid(),
353
- manager: PositionRefSchema.nullable(),
354
- relationshipType: z.literal("solid"),
355
- message: z.string(),
356
- });
357
-
358
- export type SetSeatManagerResponse = z.infer<
359
- typeof SetSeatManagerResponseSchema
360
- >;
361
-
362
- // ---------------------------------------------------------------------------
363
- // POST /api/chat/interactive-tasks/submit
364
- // ---------------------------------------------------------------------------
365
-
366
- /**
367
- * Response for submitting a filled-in interactive chat task surface (e.g. the
368
- * editable "change reporting manager" form). The submit endpoint runs the
369
- * Deterministic Execution & Governance Engine: it creates an execution and,
370
- * when no further confirmation is required, drives it to execution in the same
371
- * request. The frontend then drives the surface's lifecycle card off
372
- * `executionId` (the execution-lifecycle stream), exactly like a confirmation.
373
- *
374
- * See ADR-CONTRACTS-068.
375
- */
376
- export const SubmitInteractiveTaskResponseSchema = z.object({
377
- /** Lifecycle record id created for this submission. */
378
- executionId: z.string(),
379
- /** Coarse lifecycle status at the time the request returns. */
380
- status: z.enum(["executing", "completed", "failed", "pending_confirmation"]),
381
- });
382
-
383
- export type SubmitInteractiveTaskResponse = z.infer<
384
- typeof SubmitInteractiveTaskResponseSchema
385
- >;
386
-
387
- // ---------------------------------------------------------------------------
388
- // GET /api/user/orgs
389
- // ---------------------------------------------------------------------------
390
-
391
- const UserOrgMembershipSchema = z.object({
392
- userId: z.string(),
393
- orgId: z.string(),
394
- orgName: z.string(),
395
- orgSlug: z.string(),
396
- role: OrgChartRoleSchema.nullable(),
397
- joinedAt: z.string(),
398
- isActive: z.boolean(),
399
- orgType: z.enum(["personal", "shared"]),
400
- });
401
-
402
- export const UserOrgsResponseSchema = z.object({
403
- orgs: z.array(UserOrgMembershipSchema),
404
- });
405
-
406
- export type UserOrgsResponse = z.infer<typeof UserOrgsResponseSchema>;
407
-
408
- // ---------------------------------------------------------------------------
409
- // POST /api/user/active-org
410
- // ---------------------------------------------------------------------------
411
-
412
- export const SetActiveOrgResponseSchema = z.object({
413
- success: z.literal(true),
414
- activeOrgId: z.string(),
415
- });
416
-
417
- export type SetActiveOrgResponse = z.infer<typeof SetActiveOrgResponseSchema>;
418
-
419
- // ---------------------------------------------------------------------------
420
- // POST /api/user/orgs/:orgId/leave
421
- // ---------------------------------------------------------------------------
422
-
423
- export const LeaveOrgResponseSchema = z.object({
424
- success: z.literal(true),
425
- nextOrgId: z.string().nullable(),
426
- });
427
-
428
- export type LeaveOrgResponse = z.infer<typeof LeaveOrgResponseSchema>;
429
-
430
- // ---------------------------------------------------------------------------
431
- // GET /api/scope/check
432
- // ---------------------------------------------------------------------------
433
-
434
- export const ScopeCheckResponseSchema = z.object({
435
- hasAccess: z.boolean(),
436
- });
437
-
438
- export type ScopeCheckResponse = z.infer<typeof ScopeCheckResponseSchema>;
439
-
440
- // ---------------------------------------------------------------------------
441
- // POST /api/scope/check-batch
442
- // ---------------------------------------------------------------------------
443
-
444
- export const ScopeCheckBatchResponseSchema = z.object({
445
- results: z.record(z.string(), z.object({ hasAccess: z.boolean() })),
446
- });
447
-
448
- export type ScopeCheckBatchResponse = z.infer<
449
- typeof ScopeCheckBatchResponseSchema
450
- >;
451
-
452
- // =============================================================================
453
- // Mutation Capabilities (GET /api/capabilities) — ADR-BE-241 / PRD-00707 F4
454
- // =============================================================================
455
- // Canonical home for the viewer's resolved mutation-capability projection. The
456
- // backend derives it (src/capabilities/derive.ts) and validates the response
457
- // against CapabilitiesResponseSchema via sendValidated; the frontend reads the
458
- // inferred types to render lock/allow affordances. Previously duplicated as a
459
- // backend-local Zod + hand-written frontend interfaces — unified here.
460
-
461
- /** Scope identifiers are dot-namespaced strings (see backend trust/scopes.ts). */
462
- const RequiredScopeSchema = z.string();
463
-
464
- /** A mutable field's gate: editable, or locked with attribution (sso vs rbac). */
465
- export const FieldCapabilitySchema = z.object({
466
- state: z.enum(["editable", "locked"]),
467
- reason: z.enum(["sso", "rbac"]).optional(),
468
- requiredScope: RequiredScopeSchema.optional(),
469
- });
470
-
471
- /** An action's gate: allowed, or blocked with the scope that would unlock it. */
472
- export const ActionCapabilitySchema = z.object({
473
- state: z.enum(["allowed", "blocked"]),
474
- requiredScope: RequiredScopeSchema.optional(),
475
- });
476
-
477
- /**
478
- * The single validated capability surface the UI reads. Every sub-object is
479
- * optional, matching the partial derivation in deriveCapabilities() (actions/org
480
- * are present only when scopes resolved).
481
- */
482
- export const CapabilitiesResponseSchema = z.object({
483
- profile: z
484
- .object({
485
- fullName: FieldCapabilitySchema,
486
- preferredName: FieldCapabilitySchema,
487
- })
488
- .optional(),
489
- org: z
490
- .object({
491
- name: FieldCapabilitySchema,
492
- })
493
- .optional(),
494
- actions: z
495
- .object({
496
- members: z.object({
497
- invite: ActionCapabilitySchema,
498
- remove: ActionCapabilitySchema,
499
- changeRole: ActionCapabilitySchema,
500
- }),
501
- orgAdmins: z.object({
502
- manage: ActionCapabilitySchema,
503
- }),
504
- orgUnit: z.object({
505
- manage: ActionCapabilitySchema,
506
- }),
507
- // Entity ownership-transfer overrides (backend ADR-BE-259) +
508
- // sharing-management custody (backend ADR-BE-288). Optional so older
509
- // backends that don't derive them still validate. manageSharing gates the
510
- // admin Share affordance on content the admin may not be able to view.
511
- companyMd: z
512
- .object({
513
- transferOwnership: ActionCapabilitySchema,
514
- manageSharing: ActionCapabilitySchema,
515
- })
516
- .optional(),
517
- workItem: z
518
- .object({
519
- transferOwnership: ActionCapabilitySchema,
520
- manageSharing: ActionCapabilitySchema,
521
- })
522
- .optional(),
523
- meetingRecording: z
524
- .object({
525
- manageSharing: ActionCapabilitySchema,
526
- })
527
- .optional(),
528
- })
529
- .optional(),
530
- // Gates the chat composer's mention affordance (PRD-00988). Optional for the
531
- // same rollout symmetry as the action sub-objects above: a backend that does
532
- // not yet derive the flag must still validate its own response, and the spec
533
- // already carried the field before this schema did.
534
- chatMentions: z.boolean().optional(),
535
- // Gates the multi-party chat affordances — the participant header, the invite
536
- // flow, speaker labels (PRD-00990/00991). Optional for the same rollout
537
- // symmetry as `chatMentions` directly above, and it REQUIRES `chatMentions`:
538
- // inviting someone is a mention, so multi-party without mentions is not a
539
- // state the backend derives. The two are resolved from ONE flag read, not
540
- // two — the capabilities route holds a hard 4-query budget for a P0 own-data
541
- // read, and `chatMentions` already took the fourth slot.
542
- chatMultiParty: z.boolean().optional(),
543
- // Gates every AI affordance at once (PRD-01002). Optional for the same
544
- // rollout symmetry as the flags above. It is deliberately a CAPABILITY and
545
- // not a billing read: the UI needs to know whether to offer AI and what to
546
- // say when it cannot, and nothing more — the balance, the ledger and the
547
- // recovery live behind the billing endpoints, which an ordinary member has
548
- // no right to. `reason` is the one AiPauseReason a human can act on, and it
549
- // is null exactly when `aiPaused` is false.
550
- billing: z
551
- .object({
552
- aiPaused: z.boolean(),
553
- reason: z.enum(AI_PAUSE_REASONS).nullable(),
554
- })
555
- .optional(),
556
- });
557
-
558
- export type FieldCapability = z.infer<typeof FieldCapabilitySchema>;
559
- export type ActionCapability = z.infer<typeof ActionCapabilitySchema>;
560
- export type CapabilitiesResponse = z.infer<typeof CapabilitiesResponseSchema>;
22
+ export * from "./workspace-member-schemas";
23
+ export * from "./workspace-overview-schemas";
24
+ export * from "./reporting-write-schemas";
25
+ export * from "./user-org-schemas";
26
+ export * from "./capability-schemas";
@@ -0,0 +1,75 @@
1
+ /**
2
+ * User Org Schemas
3
+ *
4
+ * Zod response schemas for a user's orgs (GET /api/user/orgs, POST
5
+ * /api/user/active-org, /leave) and scope checks (GET /api/scope/check, POST
6
+ * /api/scope/check-batch). Re-exported from `./schemas`, the one import path
7
+ * for every org schema.
8
+ */
9
+ import { z } from "zod";
10
+ import { OrgChartRoleSchema } from "../permissions/orgchart-roles";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // GET /api/user/orgs
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const UserOrgMembershipSchema = z.object({
17
+ userId: z.string(),
18
+ orgId: z.string(),
19
+ orgName: z.string(),
20
+ orgSlug: z.string(),
21
+ role: OrgChartRoleSchema.nullable(),
22
+ joinedAt: z.string(),
23
+ isActive: z.boolean(),
24
+ orgType: z.enum(["personal", "shared"]),
25
+ });
26
+
27
+ export const UserOrgsResponseSchema = z.object({
28
+ orgs: z.array(UserOrgMembershipSchema),
29
+ });
30
+
31
+ export type UserOrgsResponse = z.infer<typeof UserOrgsResponseSchema>;
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // POST /api/user/active-org
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export const SetActiveOrgResponseSchema = z.object({
38
+ success: z.literal(true),
39
+ activeOrgId: z.string(),
40
+ });
41
+
42
+ export type SetActiveOrgResponse = z.infer<typeof SetActiveOrgResponseSchema>;
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // POST /api/user/orgs/:orgId/leave
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export const LeaveOrgResponseSchema = z.object({
49
+ success: z.literal(true),
50
+ nextOrgId: z.string().nullable(),
51
+ });
52
+
53
+ export type LeaveOrgResponse = z.infer<typeof LeaveOrgResponseSchema>;
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // GET /api/scope/check
57
+ // ---------------------------------------------------------------------------
58
+
59
+ export const ScopeCheckResponseSchema = z.object({
60
+ hasAccess: z.boolean(),
61
+ });
62
+
63
+ export type ScopeCheckResponse = z.infer<typeof ScopeCheckResponseSchema>;
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // POST /api/scope/check-batch
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export const ScopeCheckBatchResponseSchema = z.object({
70
+ results: z.record(z.string(), z.object({ hasAccess: z.boolean() })),
71
+ });
72
+
73
+ export type ScopeCheckBatchResponse = z.infer<
74
+ typeof ScopeCheckBatchResponseSchema
75
+ >;