@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.
- package/package.json +1 -1
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +21 -7
- package/src/index.ts +6 -72
- package/src/org/capability-schemas.ts +118 -0
- package/src/org/reporting-write-schemas.ts +71 -0
- package/src/org/schemas.ts +14 -548
- package/src/org/user-org-schemas.ts +75 -0
- package/src/org/workspace-member-schemas.ts +221 -0
- package/src/org/workspace-overview-schemas.ts +92 -0
- package/src/root-exports/README.md +4 -0
- package/src/root-exports/ci-governance.ts +73 -0
- package/src/source-sync/README.md +26 -5
- package/src/source-sync/__tests__/README.md +4 -2
- package/src/source-sync/__tests__/barrel.test.ts +8 -0
- package/src/source-sync/__tests__/scope-projection.test.ts +8 -0
- package/src/source-sync/__tests__/upkeep.test.ts +92 -1
- package/src/source-sync/index.ts +4 -0
- package/src/source-sync/schemas.ts +7 -4
- package/src/source-sync/upkeep.ts +61 -2
- package/src/source-sync/vocabulary.ts +19 -5
- package/src/workflows/__tests__/outcome.test.ts +81 -0
- package/src/workflows/deferred-route.ts +4 -0
- package/src/workflows/outcome-reasons.ts +26 -0
package/src/org/schemas.ts
CHANGED
|
@@ -1,26 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Org/Workspace Response Schemas
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
* - user-
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
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
|
-
|
|
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
|
+
>;
|