@sudobility/screenwriter_types 0.1.1 → 0.1.2

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/dist/index.js CHANGED
@@ -1,33 +1,4386 @@
1
- /**
2
- * @sudobility/screenwriter_types
3
- * Shared types and Zod schemas for the Fadewright frontend and backend.
4
- * Import from the package root only.
5
- */
6
- export * from './api/envelope.js';
7
- export * from './api/pagination.js';
8
- export * from './api/routes.js';
9
- export * from './ids/index.js';
10
- export * from './tenancy/index.js';
11
- export * from './sharing/index.js';
12
- export * from './projects/index.js';
13
- export * from './lifecycle/index.js';
14
- export * from './me/index.js';
15
- export * from './account/index.js';
16
- export * from './templates/index.js';
17
- export * from './versions/index.js';
18
- export * from './commands/index.js';
19
- export * from './formats/index.js';
20
- export * from './io/index.js';
21
- export * from './keys/index.js';
22
- export * from './ai/index.js';
23
- export * from './jobs/index.js';
24
- export * from './reads/index.js';
25
- export * from './reports/index.js';
26
- export * from './packets/index.js';
27
- export * from './assets/index.js';
28
- export * from './collab/index.js';
29
- export * from './credits/index.js';
30
- export * from './public/index.js';
31
- export * from './admin/index.js';
32
- export * from './sync/index.js';
33
- export * from './sync/codec.js';
1
+ import { z } from "zod";
2
+ import { DOCUMENT_KINDS } from "@sudobility/writing_core";
3
+ import { DOCUMENT_KINDS as DOCUMENT_KINDS2, ID_PREFIXES, idKind, isId, newId } from "@sudobility/writing_core";
4
+ import { API_KEY_FORMAT_RE, API_KEY_MAX_ACTIVE, API_KEY_SCOPES, CLIENT_HEADER, MCP_CLIENT_NAME, apiKeyCreateSchema, apiKeyScopeSchema, apiKeyUpdateSchema } from "./keys.js";
5
+ const ERROR_TABLE = [
6
+ ["UNAUTHORIZED", 401],
7
+ ["REAUTH_REQUIRED", 401],
8
+ ["API_KEY_FORBIDDEN", 403],
9
+ ["FORBIDDEN", 403],
10
+ ["ROLE_ESCALATION", 403],
11
+ ["NOT_FOUND", 404],
12
+ ["PROJECT_NOT_FOUND", 404],
13
+ ["DOCUMENT_NOT_FOUND", 404],
14
+ ["SNAPSHOT_NOT_FOUND", 404],
15
+ ["TEMPLATE_NOT_FOUND", 404],
16
+ ["ITEM_NOT_FOUND", 404],
17
+ ["ANCHOR_NOT_FOUND", 404],
18
+ ["TARGET_NOT_FOUND", 404],
19
+ ["VALIDATION", 400],
20
+ ["COMMAND_INVALID", 422],
21
+ ["TEMPLATE_INVALID", 422],
22
+ ["CONFIRMATION_MISMATCH", 400],
23
+ ["EPOCH_MISMATCH", 409],
24
+ ["DOCUMENT_LOCKED", 423],
25
+ ["DOCUMENT_BUSY", 409],
26
+ ["JOB_ALREADY_RUNNING", 409],
27
+ ["JOB_FINISHED", 409],
28
+ ["JOB_NOT_FINISHED", 409],
29
+ ["LAST_OWNER", 409],
30
+ ["ALREADY_MEMBER", 409],
31
+ ["NOT_A_MEMBER", 409],
32
+ ["PERSONAL_WORKSPACE_IMMUTABLE", 409],
33
+ ["OWNS_TEAM_WORKSPACE", 409],
34
+ ["NOT_IN_TRASH", 409],
35
+ ["PARENT_IN_TRASH", 409],
36
+ ["FOLDER_DEPTH", 409],
37
+ ["FOLDER_CYCLE", 409],
38
+ ["MOVE_FORBIDDEN", 403],
39
+ ["BUILTIN_IMMUTABLE", 409],
40
+ ["LOCATOR_AMBIGUOUS", 409],
41
+ ["INVITATION_INVALID", 410],
42
+ ["INVITATION_EXPIRED", 410],
43
+ ["INVITATION_CLOSED", 410],
44
+ ["EMAIL_MISMATCH", 403],
45
+ ["SHARE_LINK_INVALID", 404],
46
+ ["SHARE_LINK_PASSWORD_REQUIRED", 401],
47
+ ["WATERMARK_REQUIRED", 403],
48
+ ["EDIT_WINDOW_CLOSED", 409],
49
+ ["ASSET_TOO_LARGE", 413],
50
+ ["IMPORT_TOO_LARGE", 413],
51
+ ["UNSUPPORTED_FORMAT", 400],
52
+ ["IMPORT_FAILED", 400],
53
+ ["IMPORT_INVALID", 422],
54
+ ["ASSET_TYPE_REJECTED", 415],
55
+ ["ASSET_CHECKSUM_MISMATCH", 422],
56
+ ["ASSET_NOT_READY", 409],
57
+ ["ASSET_IN_USE", 409],
58
+ ["UPLOAD_INCOMPLETE", 409],
59
+ ["UPLOAD_EXPIRED", 410],
60
+ ["STORAGE_QUOTA_EXCEEDED", 507],
61
+ ["STORAGE_UNAVAILABLE", 503],
62
+ ["IMPORT_FORMAT_UNSUPPORTED", 415],
63
+ ["EXPORT_FORMAT_UNSUPPORTED", 415],
64
+ ["COMPARE_TOO_LARGE", 413],
65
+ ["LIMIT_EXCEEDED", 409],
66
+ ["RATE_LIMITED", 429],
67
+ ["QUOTA_EXCEEDED", 429],
68
+ ["INSUFFICIENT_CREDITS", 402],
69
+ ["AI_UNAVAILABLE", 503],
70
+ ["AI_INPUT_TOO_LARGE", 413],
71
+ ["AI_GENERATION_FAILED", 502],
72
+ ["AI_OUTPUT_INVALID", 502],
73
+ ["CONFIG_MISSING", 503],
74
+ ["CLIENT_TOO_OLD", 426],
75
+ ["CONTENT_CHANGED", 409],
76
+ ["STYLE_NOT_IN_TEMPLATE", 422],
77
+ ["NOT_CONTIGUOUS", 422],
78
+ ["LOCKED_CONTENT", 409],
79
+ ["NAME_COLLISION", 409],
80
+ ["KIND_MISMATCH", 422],
81
+ ["OVERLAPPING_SHOTS", 422],
82
+ ["OVERLAPPING_NODE", 422],
83
+ ["SCENES_LOCKED", 409],
84
+ ["NOT_LOCKED", 409],
85
+ ["RANGE_OUT_OF_BOUNDS", 422],
86
+ ["INVALID_TARGET", 422],
87
+ ["INVALID_DUAL_DIALOGUE", 422],
88
+ ["FOUNTAIN_PARSE_ERROR", 422],
89
+ ["UNMAPPED_STYLES", 422],
90
+ ["RANGE_TOO_LARGE", 413],
91
+ ["PAYLOAD_TOO_LARGE", 413],
92
+ ["INVALID_REGEX", 400],
93
+ ["REPORT_OPTIONS_INVALID", 400],
94
+ ["REPORT_KIND_UNAVAILABLE", 501],
95
+ ["ENTITY_IN_USE", 409],
96
+ ["VARIANT_IN_USE", 409],
97
+ ["SNAPSHOT_NOT_IN_DOCUMENT", 404],
98
+ ["ITEM_NOT_IN_SNAPSHOT", 404],
99
+ ["INVALID_LOCATOR", 400],
100
+ ["LOCATOR_NOT_FOUND", 404],
101
+ ["ROLE_NOT_ALLOWED_FOR_TARGET", 422],
102
+ ["ID_CONFLICT", 409],
103
+ ["IDEMPOTENCY_KEY_REUSED", 409],
104
+ ["IDEMPOTENCY_IN_PROGRESS", 409],
105
+ ["DEVICE_REVOKED", 403],
106
+ ["MACRO_TRIGGER_TAKEN", 409],
107
+ ["STALE_WRITE", 409],
108
+ ["HANDOFF_INVALID", 410],
109
+ ["WATERMARK_NOT_FOUND", 404],
110
+ ["OCR_UNAVAILABLE", 503],
111
+ ["GRAMMAR_UNAVAILABLE", 503],
112
+ ["AI_CONSENT_REQUIRED", 403],
113
+ ["AI_DISABLED_FOR_WORKSPACE", 403],
114
+ ["AI_EXCLUDED_DOCUMENT", 403],
115
+ ["AI_KEY_NOT_PERMITTED", 403],
116
+ ["QUOTE_EXCEEDS_MAX", 402],
117
+ ["AI_CONTENT_REFUSED", 422],
118
+ ["SCOPE_TOO_LARGE", 413],
119
+ ["SUGGESTION_UNAVAILABLE", 409],
120
+ ["JOB_KIND_DISABLED", 501],
121
+ ["JOB_KIND_NOT_GENERIC", 400],
122
+ ["JOB_FAILED", 409],
123
+ ["JOB_TIMEOUT", 504],
124
+ ["LIKENESS_CONSENT_REQUIRED", 422],
125
+ ["CONTENT_REJECTED", 422],
126
+ ["INTERNAL", 500]
127
+ ];
128
+ const API_ERROR_CODES = Object.fromEntries(
129
+ ERROR_TABLE.map(([code]) => [code, code])
130
+ );
131
+ const ERROR_STATUS = Object.fromEntries(ERROR_TABLE);
132
+ function successResponse(data) {
133
+ return { success: true, data };
134
+ }
135
+ function errorResponse(message, code, details) {
136
+ return details === void 0 ? { success: false, error: message, code } : { success: false, error: message, code, details };
137
+ }
138
+ const cursorQuerySchema = z.object({
139
+ limit: z.coerce.number().int().min(1).max(200).default(50),
140
+ cursor: z.string().regex(/^[A-Za-z0-9_-]+$/).optional()
141
+ });
142
+ const API_BASE_PATH = "/api/v1";
143
+ const r = (method, path, auth, permission = null) => ({ method, path, auth, permission });
144
+ const API_ROUTES = {
145
+ // B1
146
+ health: r("GET", "/health", "P"),
147
+ meGet: r("GET", "/me", "U,Kr"),
148
+ mePatch: r("PATCH", "/me", "U"),
149
+ // B2 workspaces
150
+ workspacesList: r("GET", "/workspaces", "U,Kr"),
151
+ workspaceGet: r("GET", "/workspaces/:wid", "U,Kr", "workspace.read"),
152
+ // B2 projects
153
+ projectsList: r("GET", "/workspaces/:wid/projects", "U,Kr", "project.read"),
154
+ projectCreate: r(
155
+ "POST",
156
+ "/workspaces/:wid/projects",
157
+ "U,Krw",
158
+ "project.create"
159
+ ),
160
+ projectGet: r("GET", "/projects/:pid", "U,Kr,L", "project.read"),
161
+ projectUpdate: r("PATCH", "/projects/:pid", "U,Krw", "project.update"),
162
+ projectTrash: r("POST", "/projects/:pid/trash", "U,Krw", "project.trash"),
163
+ projectRestore: r(
164
+ "POST",
165
+ "/projects/:pid/restore",
166
+ "U,Krw",
167
+ "project.restore"
168
+ ),
169
+ // B2 documents
170
+ documentsList: r(
171
+ "GET",
172
+ "/projects/:pid/documents",
173
+ "U,Kr,L",
174
+ "document.read"
175
+ ),
176
+ documentCreate: r(
177
+ "POST",
178
+ "/projects/:pid/documents",
179
+ "U,Krw",
180
+ "document.create"
181
+ ),
182
+ documentGet: r("GET", "/documents/:did", "U,Kr,L", "document.read"),
183
+ documentUpdate: r("PATCH", "/documents/:did", "U,Krw", "document.edit"),
184
+ documentTrash: r("POST", "/documents/:did/trash", "U,Krw", "document.trash"),
185
+ documentRestore: r(
186
+ "POST",
187
+ "/documents/:did/restore",
188
+ "U,Krw",
189
+ "document.restore"
190
+ ),
191
+ documentState: r("GET", "/documents/:did/state", "U,Kr,L", "document.read"),
192
+ documentContent: r(
193
+ "GET",
194
+ "/documents/:did/content",
195
+ "U,Kr,L",
196
+ "document.read"
197
+ ),
198
+ // B2 templates
199
+ templatesList: r("GET", "/templates", "U,Kr"),
200
+ templateGet: r("GET", "/templates/:tid", "U,Kr"),
201
+ // B3 sync (WebSocket upgrade, credentials travel in the first `auth` frame)
202
+ sync: r("GET", "/sync", "P"),
203
+ // B4 versions and snapshots
204
+ versionsList: r("GET", "/documents/:did/versions", "U,Kr,L", "document.read"),
205
+ versionState: r(
206
+ "GET",
207
+ "/documents/:did/versions/:vid/state",
208
+ "U,Kr,L",
209
+ "document.read"
210
+ ),
211
+ versionRestore: r(
212
+ "POST",
213
+ "/documents/:did/versions/:vid/restore",
214
+ "U,Krw",
215
+ "document.restore"
216
+ ),
217
+ versionSnapshot: r(
218
+ "POST",
219
+ "/documents/:did/versions/:vid/snapshot",
220
+ "U,Krw",
221
+ "snapshot.create"
222
+ ),
223
+ snapshotsList: r(
224
+ "GET",
225
+ "/documents/:did/snapshots",
226
+ "U,Kr,L",
227
+ "snapshot.read"
228
+ ),
229
+ snapshotCreate: r(
230
+ "POST",
231
+ "/documents/:did/snapshots",
232
+ "U,Krw",
233
+ "snapshot.create"
234
+ ),
235
+ snapshotGet: r("GET", "/snapshots/:sid", "U,Kr,L", "snapshot.read"),
236
+ snapshotState: r("GET", "/snapshots/:sid/state", "U,Kr,L", "snapshot.read"),
237
+ snapshotContent: r(
238
+ "GET",
239
+ "/snapshots/:sid/content",
240
+ "U,Kr,L",
241
+ "snapshot.read"
242
+ ),
243
+ snapshotOpen: r("POST", "/snapshots/:sid/open", "U,Krw", "snapshot.open"),
244
+ snapshotFork: r("POST", "/snapshots/:sid/fork", "U,Krw", "snapshot.read"),
245
+ // B15 versions, snapshots and sync completion (spec 05 §6.7.1, §6.9-6.11)
246
+ elementHistory: r(
247
+ "GET",
248
+ "/documents/:did/elements/:elementId/history",
249
+ "U,Kr",
250
+ "document.read"
251
+ ),
252
+ documentPresence: r(
253
+ "GET",
254
+ "/documents/:did/presence",
255
+ "U,Kr",
256
+ "document.read"
257
+ ),
258
+ versionRestoreAsCopy: r(
259
+ "POST",
260
+ "/documents/:did/versions/:vid/restore-as-copy",
261
+ "U,Krw",
262
+ "version.restoreAsCopy"
263
+ ),
264
+ snapshotPrefs: r("PUT", "/snapshots/:sid/prefs", "U", "snapshot.hideForSelf"),
265
+ snapshotNotesList: r(
266
+ "GET",
267
+ "/snapshots/:sid/notes",
268
+ "U,Kr,L",
269
+ "snapshot.read"
270
+ ),
271
+ snapshotNoteCreate: r(
272
+ "POST",
273
+ "/snapshots/:sid/notes",
274
+ "U,Krw",
275
+ "snapshot.appendNote"
276
+ ),
277
+ snapshotCommentsList: r(
278
+ "GET",
279
+ "/snapshots/:sid/comments",
280
+ "U,Kr,L",
281
+ "snapshot.read"
282
+ ),
283
+ snapshotCommentCreate: r(
284
+ "POST",
285
+ "/snapshots/:sid/comments",
286
+ "U,L",
287
+ "document.comment"
288
+ ),
289
+ snapshotCommentCopyToLive: r(
290
+ "POST",
291
+ "/snapshot-comments/:cid/copy-to-live",
292
+ "U",
293
+ "document.edit"
294
+ ),
295
+ snapshotCommentResolve: r(
296
+ "POST",
297
+ "/snapshot-comments/:cid/resolve",
298
+ "U",
299
+ "document.comment"
300
+ ),
301
+ documentCompare: r(
302
+ "POST",
303
+ "/documents/:did/compare",
304
+ "U,Kr,L",
305
+ "snapshot.read"
306
+ ),
307
+ // Import and export (synchronous, no jobs)
308
+ documentImport: r(
309
+ "POST",
310
+ "/projects/:pid/documents/import",
311
+ "U,Krw",
312
+ "document.create"
313
+ ),
314
+ documentExport: r("POST", "/documents/:did/export", "U,Kr", "document.read"),
315
+ formatsList: r("GET", "/formats", "U,Kr"),
316
+ // B5 commands and reads
317
+ commands: r("POST", "/documents/:did/commands", "U,Krw", "document.edit"),
318
+ outline: r("GET", "/documents/:did/outline", "U,Kr,L", "document.read"),
319
+ scene: r("GET", "/documents/:did/scenes/:sceneId", "U,Kr,L", "document.read"),
320
+ scenesBatch: r(
321
+ "POST",
322
+ "/documents/:did/scenes/batch",
323
+ "U,Kr,L",
324
+ "document.read"
325
+ ),
326
+ elementsBatch: r(
327
+ "POST",
328
+ "/documents/:did/elements/batch",
329
+ "U,Kr,L",
330
+ "document.read"
331
+ ),
332
+ // B6 personal API keys (user principals only; a key gets 403 API_KEY_FORBIDDEN)
333
+ apiKeysList: r("GET", "/api-keys", "U"),
334
+ apiKeyCreate: r("POST", "/api-keys", "U", "apiKeys.use"),
335
+ apiKeyUpdate: r("PATCH", "/api-keys/:kid", "U"),
336
+ apiKeyRevoke: r("DELETE", "/api-keys/:kid", "U"),
337
+ // B7 AI: script review and polish. Keys need the `ai` flag (403 AI_KEY_NOT_PERMITTED) and, to start
338
+ // jobs or decide suggestions, read_write scope.
339
+ aiStatus: r("GET", "/ai/status", "U,Kr"),
340
+ aiJobCreate: r("POST", "/documents/:did/ai/jobs", "U,Krw", "ai.run"),
341
+ aiJobsList: r("GET", "/documents/:did/ai/jobs", "U,Kr", "document.read"),
342
+ aiJobGet: r("GET", "/ai/jobs/:jobId", "U,Kr", "document.read"),
343
+ aiJobCancel: r("POST", "/ai/jobs/:jobId/cancel", "U,Krw", "ai.review"),
344
+ aiSuggestionSetsList: r(
345
+ "GET",
346
+ "/documents/:did/ai/suggestion-sets",
347
+ "U,Kr",
348
+ "document.read"
349
+ ),
350
+ aiSuggestionSetGet: r(
351
+ "GET",
352
+ "/ai/suggestion-sets/:ssid",
353
+ "U,Kr",
354
+ "document.read"
355
+ ),
356
+ aiSuggestionSetAccept: r(
357
+ "POST",
358
+ "/ai/suggestion-sets/:ssid/accept",
359
+ "U,Krw",
360
+ "document.edit"
361
+ ),
362
+ aiSuggestionSetReject: r(
363
+ "POST",
364
+ "/ai/suggestion-sets/:ssid/reject",
365
+ "U,Krw",
366
+ "document.edit"
367
+ ),
368
+ // B8 tenancy, roles, sharing. Keys (K) only read members and /me/shared; everything else is user-only (403 API_KEY_FORBIDDEN).
369
+ workspaceCreate: r("POST", "/workspaces", "U"),
370
+ workspaceUpdate: r("PATCH", "/workspaces/:wid", "U", "workspace.update"),
371
+ workspaceDelete: r("DELETE", "/workspaces/:wid", "U", "workspace.delete"),
372
+ workspaceTransfer: r(
373
+ "POST",
374
+ "/workspaces/:wid/transfer",
375
+ "U",
376
+ "workspace.transfer"
377
+ ),
378
+ workspaceLeave: r("POST", "/workspaces/:wid/leave", "U", "workspace.leave"),
379
+ workspaceUsage: r("GET", "/workspaces/:wid/usage", "U", "workspace.read"),
380
+ workspaceAudit: r(
381
+ "GET",
382
+ "/workspaces/:wid/audit.csv",
383
+ "U",
384
+ "workspace.audit"
385
+ ),
386
+ membersList: r("GET", "/workspaces/:wid/members", "U,Kr", "members.read"),
387
+ memberUpdate: r(
388
+ "PATCH",
389
+ "/workspaces/:wid/members/:uid",
390
+ "U",
391
+ "members.updateRole"
392
+ ),
393
+ memberRemove: r(
394
+ "DELETE",
395
+ "/workspaces/:wid/members/:uid",
396
+ "U",
397
+ "members.remove"
398
+ ),
399
+ invitationCreateWorkspace: r(
400
+ "POST",
401
+ "/workspaces/:wid/invitations",
402
+ "U",
403
+ "members.invite"
404
+ ),
405
+ invitationCreateProject: r(
406
+ "POST",
407
+ "/projects/:pid/invitations",
408
+ "U",
409
+ "share.manage"
410
+ ),
411
+ invitationCreateDocument: r(
412
+ "POST",
413
+ "/documents/:did/invitations",
414
+ "U",
415
+ "share.manage"
416
+ ),
417
+ workspaceInvitationsList: r(
418
+ "GET",
419
+ "/workspaces/:wid/invitations",
420
+ "U",
421
+ "members.invite"
422
+ ),
423
+ myInvitations: r("GET", "/me/invitations", "U"),
424
+ invitationRenew: r("POST", "/invitations/:iid/renew", "U"),
425
+ invitationCancel: r("DELETE", "/invitations/:iid", "U"),
426
+ invitationAccept: r("POST", "/invitations/accept", "U"),
427
+ invitationDecline: r("POST", "/invitations/:iid/decline", "U"),
428
+ sharedWithMe: r("GET", "/me/shared", "U,Kr"),
429
+ documentLock: r("POST", "/documents/:did/lock", "U", "document.lock"),
430
+ documentUnlock: r("DELETE", "/documents/:did/lock", "U", "document.lock"),
431
+ documentUnlockSession: r(
432
+ "POST",
433
+ "/documents/:did/unlock-session",
434
+ "U",
435
+ "document.read"
436
+ ),
437
+ shareLinkCreateDocument: r(
438
+ "POST",
439
+ "/documents/:did/share-links",
440
+ "U",
441
+ "share.manage"
442
+ ),
443
+ shareLinkCreateProject: r(
444
+ "POST",
445
+ "/projects/:pid/share-links",
446
+ "U",
447
+ "share.manage"
448
+ ),
449
+ shareLinkCreateSnapshot: r(
450
+ "POST",
451
+ "/snapshots/:sid/share-links",
452
+ "U",
453
+ "share.manage"
454
+ ),
455
+ shareLinksListDocument: r(
456
+ "GET",
457
+ "/documents/:did/share-links",
458
+ "U",
459
+ "share.manage"
460
+ ),
461
+ shareLinksListProject: r(
462
+ "GET",
463
+ "/projects/:pid/share-links",
464
+ "U",
465
+ "share.manage"
466
+ ),
467
+ shareLinkUpdate: r("PATCH", "/share-links/:lid", "U", "share.manage"),
468
+ shareLinkRevoke: r("DELETE", "/share-links/:lid", "U", "share.manage"),
469
+ grantsListDocument: r("GET", "/documents/:did/grants", "U", "share.manage"),
470
+ grantsListProject: r("GET", "/projects/:pid/grants", "U", "share.manage"),
471
+ grantUpdate: r("PATCH", "/grants/:gid", "U", "share.manage"),
472
+ grantRemove: r("DELETE", "/grants/:gid", "U", "share.manage"),
473
+ // B8 public link routes: the token in the path is the credential; an optional Bearer is a user or a link session (`fls_`).
474
+ publicShareResolve: r("GET", "/public/share/:token", "P"),
475
+ shareUnlock: r("POST", "/share/:token/unlock", "P"),
476
+ shareState: r("GET", "/share/:token/state", "P,L", "document.read"),
477
+ // B9 generic jobs (spec 05 §6.19). Jobs are visible to their owner, or to anyone who can read the job's document;
478
+ // cancel, outputs and recipients are owner-only. `/ai/jobs*` stays as the AI facade over the same rows.
479
+ jobsList: r("GET", "/jobs", "U,Kr", "document.read"),
480
+ jobGet: r("GET", "/jobs/:jobId", "U,Kr,L", "document.read"),
481
+ jobCreate: r("POST", "/jobs", "U,Krw", "document.read"),
482
+ jobCancel: r("POST", "/jobs/:jobId/cancel", "U,Krw", "document.read"),
483
+ jobOutputs: r("GET", "/jobs/:jobId/outputs", "U,K,L", "document.export"),
484
+ jobRecipients: r("GET", "/jobs/:jobId/recipients", "U", "document.export"),
485
+ // Provider webhook: unauthenticated, HMAC-signed with JOB_WEBHOOK_SECRET_<ADAPTER> (fails closed: 401 when unset).
486
+ jobWebhook: r("POST", "/webhooks/jobs/:adapterId", "W"),
487
+ // B10 projection-backed reads, search, reports, packets (spec 05 §6.7.1, §6.7.2, §6.12, §6.14, §6.15, §6.17, §6.18, §6.22).
488
+ // `?source=live|snapshot:<id>|version:<id>` selects the state a read answers from. Keys (K) read all of these; the report
489
+ // and resolve POSTs are reads, so a read-only key may send them.
490
+ entitiesList: r("GET", "/documents/:did/entities", "U,Kr,L", "document.read"),
491
+ entityGet: r(
492
+ "GET",
493
+ "/documents/:did/entities/:eid",
494
+ "U,Kr,L",
495
+ "document.read"
496
+ ),
497
+ entityUsage: r(
498
+ "GET",
499
+ "/documents/:did/entities/:eid/usage",
500
+ "U,Kr,L",
501
+ "document.read"
502
+ ),
503
+ entityDialogue: r(
504
+ "GET",
505
+ "/documents/:did/entities/:eid/dialogue",
506
+ "U,Kr,L",
507
+ "document.read"
508
+ ),
509
+ tagCategoriesList: r(
510
+ "GET",
511
+ "/documents/:did/tag-categories",
512
+ "U,Kr,L",
513
+ "document.read"
514
+ ),
515
+ tagsList: r("GET", "/documents/:did/tags", "U,Kr,L", "document.read"),
516
+ notesList: r("GET", "/documents/:did/notes", "U,Kr,L", "document.read"),
517
+ beatsList: r("GET", "/documents/:did/beats", "U,Kr,L", "document.read"),
518
+ binList: r("GET", "/documents/:did/bin", "U,Kr,L", "document.read"),
519
+ revisionsGet: r(
520
+ "GET",
521
+ "/documents/:did/revisions",
522
+ "U,Kr,L",
523
+ "document.read"
524
+ ),
525
+ changesList: r("GET", "/documents/:did/changes", "U,Kr,L", "document.read"),
526
+ alternatesGet: r(
527
+ "GET",
528
+ "/documents/:did/alternates/:elementId",
529
+ "U,Kr,L",
530
+ "document.read"
531
+ ),
532
+ titlePageGet: r(
533
+ "GET",
534
+ "/documents/:did/title-page",
535
+ "U,Kr,L",
536
+ "document.read"
537
+ ),
538
+ statsGet: r("GET", "/documents/:did/stats", "U,Kr,L", "document.read"),
539
+ fountainGet: r("GET", "/documents/:did/fountain", "U,Kr,L", "document.read"),
540
+ sceneShotsList: r(
541
+ "GET",
542
+ "/documents/:did/scenes/:sceneId/shots",
543
+ "U,Kr,L",
544
+ "document.read"
545
+ ),
546
+ resolveBatch: r("POST", "/documents/:did/resolve", "U,Kr,L", "document.read"),
547
+ resolveOne: r("GET", "/documents/:did/resolve", "U,Kr,L", "document.read"),
548
+ documentSearch: r("GET", "/documents/:did/search", "U,Kr,L", "document.read"),
549
+ workspaceSearch: r(
550
+ "GET",
551
+ "/workspaces/:wid/search",
552
+ "U,Kr",
553
+ "workspace.read"
554
+ ),
555
+ search: r("GET", "/search", "U,Kr"),
556
+ reportKinds: r("GET", "/reports/kinds", "U,Kr"),
557
+ reportGet: r("GET", "/documents/:did/reports/:kind", "U,Kr,L", "reports.run"),
558
+ reportCreate: r("POST", "/documents/:did/reports", "U,Kr,L", "reports.run"),
559
+ packetScene: r(
560
+ "GET",
561
+ "/documents/:did/packets/scene/:locator",
562
+ "U,Kr",
563
+ "document.read"
564
+ ),
565
+ packetCharacter: r(
566
+ "GET",
567
+ "/documents/:did/packets/character/:locator",
568
+ "U,Kr",
569
+ "document.read"
570
+ ),
571
+ packetLocation: r(
572
+ "GET",
573
+ "/documents/:did/packets/location/:locator",
574
+ "U,Kr",
575
+ "document.read"
576
+ ),
577
+ packetShot: r(
578
+ "GET",
579
+ "/documents/:did/packets/shot/:locator",
580
+ "U,Kr",
581
+ "document.read"
582
+ ),
583
+ // B13 account and per-user data (spec 05 §6.1, §6.1.1, §6.26). User-only (a key gets 403 API_KEY_FORBIDDEN) except the macro list (Kr).
584
+ meDictionaryGet: r("GET", "/me/dictionary", "U"),
585
+ meDictionaryUpdate: r("PUT", "/me/dictionary", "U"),
586
+ mePreferencesGet: r("GET", "/me/preferences", "U"),
587
+ mePreferencesSet: r("PUT", "/me/preferences", "U"),
588
+ meMacrosList: r("GET", "/me/macros", "U,Kr"),
589
+ meMacroCreate: r("POST", "/me/macros", "U"),
590
+ meMacroUpdate: r("PATCH", "/me/macros/:mid", "U"),
591
+ meMacroDelete: r("DELETE", "/me/macros/:mid", "U"),
592
+ meWritingStats: r("GET", "/me/writing-stats", "U"),
593
+ meWritingSessionCreate: r("POST", "/me/writing-sessions", "U"),
594
+ meWritingGoalsGet: r("GET", "/me/writing-goals", "U"),
595
+ meWritingGoalsSet: r("PUT", "/me/writing-goals", "U"),
596
+ meExport: r("POST", "/me/export", "U"),
597
+ meDelete: r("DELETE", "/me", "U"),
598
+ meRestore: r("POST", "/me/restore", "U"),
599
+ documentMyStateGet: r(
600
+ "GET",
601
+ "/documents/:did/my-state",
602
+ "U",
603
+ "document.read"
604
+ ),
605
+ documentMyStateSet: r(
606
+ "PUT",
607
+ "/documents/:did/my-state",
608
+ "U",
609
+ "document.read"
610
+ ),
611
+ documentStar: r("PUT", "/documents/:did/star", "U", "document.read"),
612
+ documentUnstar: r("DELETE", "/documents/:did/star", "U", "document.read"),
613
+ // B14 project, document and template lifecycle, workspace settings (spec 05 §6.2, §6.6-§6.8, §6.26). Writes are `Krw`; purge,
614
+ // trash-empty, workspace defaults and contacts are user-only (a key gets 403 API_KEY_FORBIDDEN).
615
+ workspaceDocuments: r(
616
+ "GET",
617
+ "/workspaces/:wid/documents",
618
+ "U,Kr",
619
+ "project.read"
620
+ ),
621
+ workspaceTrash: r("GET", "/workspaces/:wid/trash", "U,Kr", "project.read"),
622
+ workspaceTrashEmpty: r(
623
+ "POST",
624
+ "/workspaces/:wid/trash/empty",
625
+ "U",
626
+ "project.purge"
627
+ ),
628
+ projectPurge: r("DELETE", "/projects/:pid", "U", "project.purge"),
629
+ projectDuplicate: r(
630
+ "POST",
631
+ "/projects/:pid/duplicate",
632
+ "U,Krw",
633
+ "project.create"
634
+ ),
635
+ projectFolderCreate: r(
636
+ "POST",
637
+ "/projects/:pid/folders",
638
+ "U,Krw",
639
+ "project.update"
640
+ ),
641
+ projectFolderUpdate: r(
642
+ "PATCH",
643
+ "/project-folders/:fid",
644
+ "U,Krw",
645
+ "project.update"
646
+ ),
647
+ projectFolderDelete: r(
648
+ "DELETE",
649
+ "/project-folders/:fid",
650
+ "U,Krw",
651
+ "project.update"
652
+ ),
653
+ documentMove: r("POST", "/documents/:did/move", "U,Krw", "document.move"),
654
+ documentPurge: r("DELETE", "/documents/:did", "U", "project.purge"),
655
+ documentDuplicate: r(
656
+ "POST",
657
+ "/documents/:did/duplicate",
658
+ "U,Krw",
659
+ "document.create"
660
+ ),
661
+ documentApplyTemplate: r(
662
+ "POST",
663
+ "/documents/:did/template",
664
+ "U,Krw",
665
+ "document.lock"
666
+ ),
667
+ templateCreate: r("POST", "/templates", "U,Krw", "templates.manage"),
668
+ templateVersionCreate: r(
669
+ "POST",
670
+ "/templates/:tid/versions",
671
+ "U,Krw",
672
+ "templates.manage"
673
+ ),
674
+ templateUpdate: r("PATCH", "/templates/:tid", "U,Krw", "templates.manage"),
675
+ templateDelete: r("DELETE", "/templates/:tid", "U,Krw", "templates.manage"),
676
+ templateImport: r("POST", "/templates/import", "U,Krw", "templates.manage"),
677
+ templateExport: r("GET", "/templates/:tid/export", "U,Kr"),
678
+ projectBinList: r("GET", "/projects/:pid/bin", "U,Kr", "project.read"),
679
+ projectBinCreate: r("POST", "/projects/:pid/bin", "U,Krw", "document.edit"),
680
+ projectBinDelete: r(
681
+ "DELETE",
682
+ "/project-bin-items/:id",
683
+ "U,Krw",
684
+ "document.edit"
685
+ ),
686
+ workspaceDefaultsGet: r(
687
+ "GET",
688
+ "/workspaces/:wid/defaults",
689
+ "U,Kr",
690
+ "workspace.read"
691
+ ),
692
+ workspaceDefaultsSet: r(
693
+ "PUT",
694
+ "/workspaces/:wid/defaults",
695
+ "U",
696
+ "workspace.update"
697
+ ),
698
+ workspaceContactsList: r(
699
+ "GET",
700
+ "/workspaces/:wid/contacts",
701
+ "U",
702
+ "workspace.read"
703
+ ),
704
+ workspaceContactsCreate: r(
705
+ "POST",
706
+ "/workspaces/:wid/contacts",
707
+ "U",
708
+ "workspace.update"
709
+ ),
710
+ workspaceContactUpdate: r(
711
+ "PATCH",
712
+ "/workspace-contacts/:cid",
713
+ "U",
714
+ "workspace.update"
715
+ ),
716
+ workspaceContactDelete: r(
717
+ "DELETE",
718
+ "/workspace-contacts/:cid",
719
+ "U",
720
+ "workspace.update"
721
+ ),
722
+ // B16 imports, exports and watermark as jobs (spec 05 §6.18). `POST /imports*` and `/uploads/state` are `Krw`; exports are `Kr` (a read key may
723
+ // export what it can read); the watermark lookup is user-only (403 API_KEY_FORBIDDEN for a key). The synchronous
724
+ // `documentImport`/`documentExport`/`formatsList` stay until the app moves to these (see the plan's B16 "Deferred" list).
725
+ uploadState: r("POST", "/uploads/state", "U,Krw"),
726
+ importCreate: r("POST", "/imports", "U,Krw", "document.create"),
727
+ importStart: r("POST", "/imports/:importId/start", "U,Krw"),
728
+ documentImportOver: r(
729
+ "POST",
730
+ "/documents/:did/import-over",
731
+ "U,Krw",
732
+ "document.edit"
733
+ ),
734
+ documentExportCreate: r(
735
+ "POST",
736
+ "/documents/:did/exports",
737
+ "U,Kr",
738
+ "document.export"
739
+ ),
740
+ documentExportCombined: r(
741
+ "POST",
742
+ "/documents/export-combined",
743
+ "U,Kr",
744
+ "document.export"
745
+ ),
746
+ watermarkLookup: r(
747
+ "POST",
748
+ "/workspaces/:wid/watermark-lookup",
749
+ "U",
750
+ "workspace.audit"
751
+ ),
752
+ // B11 assets and R2 (spec 05 §6.13). Bytes go to object storage through presigned URLs, never through the API. Writes are `Krw`, reads `Kr`;
753
+ // share-link readers (`L`) are not built (no `/share/:token/assets` route yet). The uploader routes check the caller is the uploader.
754
+ assetUploadCreate: r(
755
+ "POST",
756
+ "/workspaces/:wid/assets/uploads",
757
+ "U,Krw",
758
+ "assets.upload"
759
+ ),
760
+ assetUploadGet: r("GET", "/assets/uploads/:uploadId", "U,Krw"),
761
+ assetUploadParts: r("POST", "/assets/uploads/:uploadId/parts", "U,Krw"),
762
+ assetUploadComplete: r("POST", "/assets/uploads/:uploadId/complete", "U,Krw"),
763
+ assetUploadAbort: r("DELETE", "/assets/uploads/:uploadId", "U,Krw"),
764
+ assetsList: r("GET", "/assets", "U,Kr", "assets.read"),
765
+ assetGet: r("GET", "/assets/:aid", "U,Kr", "assets.read"),
766
+ assetUrl: r("GET", "/assets/:aid/versions/:vid/url", "U,Kr", "assets.read"),
767
+ assetUpdate: r("PATCH", "/assets/:aid", "U,Krw", "assets.upload"),
768
+ assetDelete: r("DELETE", "/assets/:aid", "U,Krw", "assets.upload"),
769
+ assetRestore: r("POST", "/assets/:aid/restore", "U,Krw", "assets.upload"),
770
+ assetLinkCreate: r("POST", "/asset-links", "U,Krw", "assets.link"),
771
+ documentAssetLinks: r(
772
+ "GET",
773
+ "/documents/:did/asset-links",
774
+ "U,Kr",
775
+ "assets.read"
776
+ ),
777
+ assetLinkUpdate: r("PATCH", "/asset-links/:lid", "U,Krw", "assets.link"),
778
+ assetLinkDelete: r("DELETE", "/asset-links/:lid", "U,Krw", "assets.link"),
779
+ documentStaleness: r(
780
+ "GET",
781
+ "/documents/:did/staleness",
782
+ "U,Kr",
783
+ "document.read"
784
+ ),
785
+ assetLinkStaleness: r(
786
+ "GET",
787
+ "/asset-links/:lid/staleness",
788
+ "U,Kr",
789
+ "assets.read"
790
+ ),
791
+ // B12 collaboration, notifications, devices, email (spec 05 §6.15, §6.16, §6.24, §6.26). Most of this slice is
792
+ // user-only (a key gets 403 API_KEY_FORBIDDEN): personal notifications and devices, and chat/activity writes are
793
+ // never for a key. `documentChatList` and `documentActivity` are the two exceptions (`Kr`, a read key may read them).
794
+ meNotificationPrefsGet: r("GET", "/me/notification-prefs", "U"),
795
+ meNotificationPrefsSet: r("PUT", "/me/notification-prefs", "U"),
796
+ documentMentionsWithoutAccess: r(
797
+ "GET",
798
+ "/documents/:did/mentions-without-access",
799
+ "U",
800
+ "share.manage"
801
+ ),
802
+ documentChatList: r("GET", "/documents/:did/chat", "U,Kr,L", "document.chat"),
803
+ documentChatCreate: r("POST", "/documents/:did/chat", "U,L", "document.chat"),
804
+ chatMessageUpdate: r("PATCH", "/chat-messages/:mid", "U", "document.chat"),
805
+ chatMessageDelete: r("DELETE", "/chat-messages/:mid", "U", "document.chat"),
806
+ documentActivity: r(
807
+ "GET",
808
+ "/documents/:did/activity",
809
+ "U,Kr",
810
+ "document.activity"
811
+ ),
812
+ workspaceActivity: r(
813
+ "GET",
814
+ "/workspaces/:wid/activity",
815
+ "U",
816
+ "workspace.read"
817
+ ),
818
+ notificationsList: r("GET", "/notifications", "U"),
819
+ notificationsRead: r("POST", "/notifications/read", "U"),
820
+ notificationDelete: r("DELETE", "/notifications/:nid", "U"),
821
+ notificationsStream: r("GET", "/notifications/stream", "U"),
822
+ devicesList: r("GET", "/devices", "U"),
823
+ deviceCreate: r("POST", "/devices", "U"),
824
+ deviceUpdate: r("PATCH", "/devices/:devid", "U"),
825
+ deviceRevoke: r("POST", "/devices/:devid/revoke", "U"),
826
+ devicePushTokenSet: r("PUT", "/devices/:devid/push-token", "U"),
827
+ devicePushTokenDelete: r("DELETE", "/devices/:devid/push-token", "U"),
828
+ // B17 AI completion and credits (spec 05 §6.3, §6.19, §6.20, §6.21; spec 06 §9-10). AI routes need the key's `ai`
829
+ // flag (403 AI_KEY_NOT_PERMITTED); `decide` needs read_write. Consent, activity and credits are user-only except
830
+ // the balance/products reads (Kr; a key spends its own workspace's credits so it may read them) — `consumables/products`
831
+ // is also public (unauthenticated pricing display). `GET .../suggestion-sets` and `GET /suggestion-sets/:ssid` are
832
+ // already built (B7, under `/ai/` — kept as-is rather than duplicated at the bare path spec 05 Appendix A also lists).
833
+ meAiConsentGet: r("GET", "/me/ai-consents", "U"),
834
+ meAiConsentAccept: r("POST", "/me/ai-consents", "U"),
835
+ meAiActivity: r("GET", "/me/ai-activity", "U"),
836
+ workspaceAiActivity: r(
837
+ "GET",
838
+ "/workspaces/:wid/ai-activity",
839
+ "U",
840
+ "workspace.audit"
841
+ ),
842
+ aiEstimate: r("POST", "/documents/:did/ai/estimate", "U,Kr", "document.read"),
843
+ aiReportsList: r(
844
+ "GET",
845
+ "/documents/:did/ai/reports",
846
+ "U,Kr",
847
+ "document.read"
848
+ ),
849
+ aiReportGet: r(
850
+ "GET",
851
+ "/documents/:did/ai/reports/:jobId",
852
+ "U,Kr",
853
+ "document.read"
854
+ ),
855
+ aiReportNoteConvert: r(
856
+ "POST",
857
+ "/documents/:did/ai/reports/:jobId/notes/:noteId/convert",
858
+ "U,Krw",
859
+ "document.comment"
860
+ ),
861
+ aiSuggestionSetDecide: r(
862
+ "POST",
863
+ "/ai/suggestion-sets/:ssid/decide",
864
+ "U,Krw",
865
+ "document.edit"
866
+ ),
867
+ consumablesBalance: r("GET", "/consumables/balance", "U,Kr"),
868
+ consumablesPurchases: r("GET", "/consumables/purchases", "U"),
869
+ consumablesUsages: r("GET", "/consumables/usages", "U"),
870
+ consumablesProducts: r("GET", "/consumables/products", "U,Kr,P"),
871
+ consumablesWebhook: r("POST", "/consumables/webhook", "W"),
872
+ purchaseHandoffCreate: r("POST", "/purchases/handoff", "U"),
873
+ // B18 public, admin, telemetry (spec 05 §6.21, §6.23, §6.24; `/language/*` skipped by decision, see the plan's
874
+ // B18 note). Public routes need no session at all. Admin needs `siteAdmin` on a USER principal specifically —
875
+ // never an API key, whatever its scope — enforced in the handler (there is no per-object permission to check).
876
+ publicTemplatesList: r("GET", "/public/templates", "P"),
877
+ publicTemplateGet: r("GET", "/public/templates/:builtinKey", "P"),
878
+ publicConfig: r("GET", "/public/config", "P"),
879
+ publicNamesDb: r("GET", "/public/names-db/:version", "P"),
880
+ publicHealthDeep: r("GET", "/public/health/deep", "P"),
881
+ publicWatermarkedGet: r("GET", "/public/watermarked/:exportId/:token", "P"),
882
+ purchaseHandoffRedeem: r("POST", "/public/purchases/handoff/redeem", "P"),
883
+ telemetryCreate: r("POST", "/telemetry", "U,P"),
884
+ adminUsersLookup: r("GET", "/admin/users", "U"),
885
+ adminUserRestore: r("POST", "/admin/users/:uid/restore", "U"),
886
+ adminUserPurge: r("DELETE", "/admin/users/:uid", "U"),
887
+ adminJobsList: r("GET", "/admin/jobs", "U"),
888
+ adminJobRetry: r("POST", "/admin/jobs/:id/retry", "U"),
889
+ adminJobRefund: r("POST", "/admin/jobs/:id/refund", "U"),
890
+ adminJobKindUpdate: r("PATCH", "/admin/job-kinds/:kind", "U"),
891
+ adminDocumentMeta: r("GET", "/admin/documents/:did/meta", "U")
892
+ };
893
+ const WORKSPACE_KINDS = ["personal", "team"];
894
+ const ROLES = [
895
+ "owner",
896
+ "admin",
897
+ "writer",
898
+ "commenter",
899
+ "viewer"
900
+ ];
901
+ const workspaceKindSchema = z.enum(WORKSPACE_KINDS);
902
+ const roleSchema = z.enum(ROLES);
903
+ const PERMISSION_MIN_ROLE = {
904
+ "workspace.read": "viewer",
905
+ "workspace.update": "admin",
906
+ "workspace.aiToggle": "owner",
907
+ "workspace.audit": "admin",
908
+ "workspace.delete": "owner",
909
+ "workspace.transfer": "owner",
910
+ "workspace.leave": "viewer",
911
+ "members.read": "viewer",
912
+ "members.invite": "admin",
913
+ "members.updateRole": "admin",
914
+ "members.remove": "admin",
915
+ "project.create": "writer",
916
+ "project.read": "viewer",
917
+ "project.update": "writer",
918
+ "project.trash": "writer",
919
+ "project.restore": "writer",
920
+ "project.purge": "admin",
921
+ "document.create": "writer",
922
+ "document.read": "viewer",
923
+ "document.export": "viewer",
924
+ "document.edit": "writer",
925
+ "document.comment": "commenter",
926
+ "document.chat": "commenter",
927
+ "document.activity": "viewer",
928
+ "document.trash": "writer",
929
+ "document.restore": "writer",
930
+ "document.move": "writer",
931
+ "document.lock": "writer",
932
+ "document.aiExclude": "writer",
933
+ "snapshot.create": "writer",
934
+ "snapshot.open": "writer",
935
+ "snapshot.fork": "writer",
936
+ "snapshot.cherryPick": "writer",
937
+ "snapshot.appendNote": "writer",
938
+ "snapshot.read": "viewer",
939
+ "snapshot.compare": "viewer",
940
+ "snapshot.hideForSelf": "viewer",
941
+ "version.restore": "writer",
942
+ "version.restoreAsCopy": "writer",
943
+ "share.manage": "writer",
944
+ "assets.upload": "writer",
945
+ "assets.link": "writer",
946
+ "assets.read": "viewer",
947
+ "ai.run": "writer",
948
+ "ai.review": "commenter",
949
+ "jobs.media": "writer",
950
+ "templates.manage": "writer",
951
+ "reports.run": "viewer",
952
+ "apiKeys.use": "viewer"
953
+ };
954
+ const PERMISSIONS = Object.keys(PERMISSION_MIN_ROLE);
955
+ const RANK = {
956
+ owner: 0,
957
+ admin: 1,
958
+ writer: 2,
959
+ commenter: 3,
960
+ viewer: 4
961
+ };
962
+ const roleAtLeast = (a, b) => RANK[a] <= RANK[b];
963
+ function maxRole(...roles) {
964
+ let best = null;
965
+ for (const r2 of roles)
966
+ if (r2 && (best === null || RANK[r2] < RANK[best])) best = r2;
967
+ return best;
968
+ }
969
+ const minRole = (a, b) => RANK[a] >= RANK[b] ? a : b;
970
+ const ROLE_PERMISSIONS = Object.fromEntries(
971
+ ROLES.map((role) => [
972
+ role,
973
+ PERMISSIONS.filter((p) => roleAtLeast(role, PERMISSION_MIN_ROLE[p]))
974
+ ])
975
+ );
976
+ const hasPermission = (role, permission) => roleAtLeast(role, PERMISSION_MIN_ROLE[permission]);
977
+ const requiredRoleFor = (permission) => PERMISSION_MIN_ROLE[permission];
978
+ const SHARE_ROLES = ["admin", "writer", "commenter", "viewer"];
979
+ const TEAM_WORKSPACE_LIMIT = 50;
980
+ const workspaceCreateSchema = z.object({
981
+ name: z.string().trim().min(1).max(80),
982
+ avatarAssetId: z.string().min(1).nullable().optional()
983
+ });
984
+ const workspaceUpdateSchema = z.object({
985
+ name: z.string().trim().min(1).max(80).optional(),
986
+ avatarAssetId: z.string().min(1).nullable().optional(),
987
+ defaultTemplateId: z.string().min(1).nullable().optional(),
988
+ defaultLanguage: z.string().min(2).max(35).optional(),
989
+ aiEnabled: z.boolean().optional(),
990
+ allowPublicLinks: z.boolean().optional()
991
+ });
992
+ const workspaceDeleteSchema = z.object({ confirmName: z.string() });
993
+ const workspaceTransferSchema = z.object({
994
+ toUserId: z.string().min(1)
995
+ });
996
+ const workspaceAuditQuerySchema = z.object({
997
+ from: z.iso.datetime({ offset: true }).optional(),
998
+ to: z.iso.datetime({ offset: true }).optional()
999
+ });
1000
+ const AUDIT_CSV_COLUMNS = [
1001
+ "created_at",
1002
+ "actor",
1003
+ "action",
1004
+ "target_type",
1005
+ "target_id",
1006
+ "ip"
1007
+ ];
1008
+ const AUDIT_MAX_RANGE_DAYS = 366;
1009
+ const memberUpdateSchema = z.object({ role: roleSchema });
1010
+ const INVITATION_TARGETS = ["workspace", "project", "document"];
1011
+ const INVITATION_TTL_DAYS = 14;
1012
+ const INVITATION_STATUSES = [
1013
+ "pending",
1014
+ "accepted",
1015
+ "declined",
1016
+ "cancelled",
1017
+ "expired"
1018
+ ];
1019
+ const invitationCreateSchema = z.object({
1020
+ email: z.email().max(254),
1021
+ role: roleSchema
1022
+ });
1023
+ const invitationAcceptSchema = z.object({ token: z.string().min(1) });
1024
+ const grantUpdateSchema = z.object({ role: roleSchema });
1025
+ const SHARE_LINK_ACCESS = ["view", "comment"];
1026
+ const SHARE_LINK_GENERAL_ACCESS = ["anyone", "restricted"];
1027
+ const SHARE_LINK_TARGETS = ["project", "document", "snapshot"];
1028
+ const SHARE_LINK_TOKEN_PREFIX = "fws_";
1029
+ const LINK_SESSION_TTL_S = 900;
1030
+ const LINK_SESSION_PREFIX = "fls_";
1031
+ const shareLinkCreateSchema = z.object({
1032
+ access: z.enum(SHARE_LINK_ACCESS),
1033
+ generalAccess: z.enum(SHARE_LINK_GENERAL_ACCESS).default("anyone"),
1034
+ expiresAt: z.iso.datetime({ offset: true }).nullable().optional(),
1035
+ password: z.string().min(1).max(200).optional(),
1036
+ allowDownload: z.boolean().default(true),
1037
+ watermarkText: z.string().max(200).nullable().optional()
1038
+ });
1039
+ const shareLinkUpdateSchema = z.object({
1040
+ access: z.enum(SHARE_LINK_ACCESS).optional(),
1041
+ generalAccess: z.enum(SHARE_LINK_GENERAL_ACCESS).optional(),
1042
+ expiresAt: z.iso.datetime({ offset: true }).nullable().optional(),
1043
+ password: z.string().min(1).max(200).optional(),
1044
+ clearPassword: z.boolean().optional(),
1045
+ allowDownload: z.boolean().optional(),
1046
+ watermarkText: z.string().max(200).nullable().optional()
1047
+ }).refine((v) => !(v.password !== void 0 && v.clearPassword), {
1048
+ message: "password and clearPassword are exclusive"
1049
+ });
1050
+ const shareUnlockSchema = z.object({
1051
+ password: z.string().max(200).optional()
1052
+ });
1053
+ const UNLOCK_SESSION_TTL_S = 1800;
1054
+ const REAUTH_MAX_AGE_S = 300;
1055
+ const documentKindSchema = z.enum(DOCUMENT_KINDS);
1056
+ const SORT_OPTIONS = ["updated", "name", "created"];
1057
+ const PROJECT_KINDS = ["feature", "series", "play", "other"];
1058
+ const projectKindSchema = z.enum(PROJECT_KINDS);
1059
+ const DOCUMENT_LABELS_MAX = 20;
1060
+ const DOCUMENT_LABEL_MAX_CHARS = 40;
1061
+ const documentLabelsSchema = z.array(z.string().trim().min(1).max(DOCUMENT_LABEL_MAX_CHARS)).max(DOCUMENT_LABELS_MAX);
1062
+ const projectCreateSchema = z.object({
1063
+ name: z.string().trim().min(1).max(200),
1064
+ description: z.string().max(2e3).optional(),
1065
+ coverAssetId: z.string().optional(),
1066
+ color: z.string().max(32).optional(),
1067
+ kind: projectKindSchema.optional(),
1068
+ logline: z.string().max(2e3).optional()
1069
+ });
1070
+ const projectUpdateSchema = z.object({
1071
+ name: z.string().trim().min(1).max(200).optional(),
1072
+ description: z.string().max(2e3).nullable().optional(),
1073
+ coverAssetId: z.string().nullable().optional(),
1074
+ color: z.string().max(32).nullable().optional(),
1075
+ kind: projectKindSchema.optional(),
1076
+ logline: z.string().max(2e3).nullable().optional(),
1077
+ workspaceId: z.string().optional()
1078
+ });
1079
+ const projectListQuerySchema = z.object({
1080
+ trashed: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
1081
+ q: z.string().optional(),
1082
+ sort: z.enum(SORT_OPTIONS).optional()
1083
+ });
1084
+ const documentCreateSchema = z.object({
1085
+ /** Client-generated `doc_...`; creation is idempotent on it. */
1086
+ id: z.string().startsWith("doc_").optional(),
1087
+ title: z.string().trim().min(1).max(200),
1088
+ kind: documentKindSchema,
1089
+ folderId: z.string().optional(),
1090
+ templateId: z.string().optional(),
1091
+ templateVersion: z.number().int().positive().optional(),
1092
+ language: z.string().max(16).optional(),
1093
+ labels: documentLabelsSchema.optional(),
1094
+ season: z.number().int().min(0).max(9999).optional(),
1095
+ episode: z.number().int().min(0).max(99999).optional()
1096
+ });
1097
+ const documentUpdateSchema = z.object({
1098
+ title: z.string().trim().min(1).max(200).optional(),
1099
+ folderId: z.string().nullable().optional(),
1100
+ position: z.number().optional(),
1101
+ color: z.string().max(32).nullable().optional(),
1102
+ language: z.string().max(16).optional(),
1103
+ logline: z.string().max(2e3).nullable().optional(),
1104
+ excludeFromAi: z.boolean().optional(),
1105
+ labels: documentLabelsSchema.optional(),
1106
+ season: z.number().int().min(0).max(9999).nullable().optional(),
1107
+ episode: z.number().int().min(0).max(99999).nullable().optional()
1108
+ });
1109
+ const documentListQuerySchema = z.object({
1110
+ trashed: z.enum(["true", "false"]).transform((v) => v === "true").optional(),
1111
+ folderId: z.string().optional(),
1112
+ kind: documentKindSchema.optional(),
1113
+ /** `episode` = `(season, episode)` with unnumbered documents last; the default for a `series` project. */
1114
+ order: z.enum(["position", "episode"]).optional()
1115
+ });
1116
+ const TRASH_RETENTION_DAYS = 30;
1117
+ const FOLDER_MAX_DEPTH = 8;
1118
+ const projectFolderCreateSchema = z.object({
1119
+ name: z.string().trim().min(1).max(120),
1120
+ parentFolderId: z.string().min(1).optional()
1121
+ });
1122
+ const projectFolderUpdateSchema = z.object({
1123
+ name: z.string().trim().min(1).max(120).optional(),
1124
+ /** `null` moves the folder to the project root. */
1125
+ parentFolderId: z.string().min(1).nullable().optional(),
1126
+ position: z.number().finite().optional()
1127
+ });
1128
+ const projectFolderDeleteQuerySchema = z.object({
1129
+ moveContentsTo: z.string().min(1).optional()
1130
+ });
1131
+ const projectPurgeSchema = z.object({ confirmName: z.string() });
1132
+ const projectDuplicateSchema = z.object({
1133
+ name: z.string().trim().min(1).max(200),
1134
+ targetWorkspaceId: z.string().min(1).optional(),
1135
+ includeSnapshots: z.boolean().default(false),
1136
+ /** Re-links the source's asset links onto the copy (B11); a no-op when the target is a different workspace. */
1137
+ includeAssets: z.boolean().default(false)
1138
+ });
1139
+ const documentMoveSchema = z.object({
1140
+ targetProjectId: z.string().min(1),
1141
+ folderId: z.string().min(1).nullable().optional()
1142
+ });
1143
+ const documentDuplicateSchema = z.object({
1144
+ title: z.string().trim().min(1).max(200),
1145
+ targetProjectId: z.string().min(1).optional(),
1146
+ includeSnapshots: z.boolean().default(false),
1147
+ includeAssets: z.boolean().default(false)
1148
+ });
1149
+ const documentApplyTemplateSchema = z.object({
1150
+ templateId: z.string().min(1),
1151
+ templateVersion: z.number().int().min(1).optional(),
1152
+ /** Old style id -> style id of the new template, for styles the new template has no counterpart of. */
1153
+ mapping: z.record(z.string(), z.string()).optional(),
1154
+ dryRun: z.boolean().optional()
1155
+ });
1156
+ const boolQuery$2 = z.enum(["true", "false"]).transform((v) => v === "true");
1157
+ const workspaceDocumentsQuerySchema = cursorQuerySchema.extend({
1158
+ q: z.string().max(200).optional(),
1159
+ kind: documentKindSchema.optional(),
1160
+ trashed: boolQuery$2.optional(),
1161
+ starred: boolQuery$2.optional(),
1162
+ label: z.string().max(40).optional()
1163
+ });
1164
+ const TRASH_ITEM_TYPES = ["project", "document", "asset"];
1165
+ const trashEmptySchema = z.object({ confirm: z.string() });
1166
+ const purgeInputSchema = z.object({
1167
+ workspaceId: z.string().optional(),
1168
+ projectId: z.string().optional(),
1169
+ documentId: z.string().optional()
1170
+ });
1171
+ const PROJECT_BIN_ITEM_MAX_BYTES = 2 * 1024 * 1024;
1172
+ const projectBinCreateSchema = z.object({
1173
+ id: z.string().startsWith("bin_").optional(),
1174
+ title: z.string().trim().min(1).max(200).optional(),
1175
+ elements: z.array(z.record(z.string(), z.unknown())).min(1),
1176
+ sourceDocumentId: z.string().min(1).optional()
1177
+ });
1178
+ const DEFAULT_PROJECT_ROLES = [
1179
+ "admin",
1180
+ "writer",
1181
+ "commenter",
1182
+ "viewer"
1183
+ ];
1184
+ const looseItems = (max2) => z.array(z.looseObject({})).max(max2);
1185
+ const workspaceDefaultsSchema = z.object({
1186
+ tagCategories: looseItems(200).default([]),
1187
+ revisionColourSets: looseItems(100).default([]),
1188
+ noteTypes: looseItems(100).default([]),
1189
+ worksheets: looseItems(200).default([]),
1190
+ /** The role newly created projects grant workspace members who have none (F-SET-009). */
1191
+ defaultProjectRole: z.enum(DEFAULT_PROJECT_ROLES).nullable().default(null)
1192
+ });
1193
+ const WORKSPACE_DEFAULTS_MAX_BYTES = 256 * 1024;
1194
+ const workspaceDefaultsPutSchema = workspaceDefaultsSchema.extend({
1195
+ baseUpdatedAt: z.string().min(1)
1196
+ });
1197
+ const WORKSPACE_CONTACTS_BATCH_MAX = 500;
1198
+ const contactFields = {
1199
+ name: z.string().trim().min(1).max(200),
1200
+ company: z.string().trim().max(200).nullable().optional(),
1201
+ email: z.string().trim().max(320).nullable().optional()
1202
+ };
1203
+ const workspaceContactInputSchema = z.object({
1204
+ id: z.string().startsWith("ctc_").optional(),
1205
+ ...contactFields
1206
+ });
1207
+ const workspaceContactsCreateSchema = z.object({
1208
+ contacts: z.array(workspaceContactInputSchema).min(1)
1209
+ });
1210
+ const workspaceContactPatchSchema = z.object({
1211
+ name: contactFields.name.optional(),
1212
+ company: contactFields.company,
1213
+ email: contactFields.email
1214
+ });
1215
+ const workspaceContactsQuerySchema = cursorQuerySchema.extend({
1216
+ q: z.string().max(200).optional()
1217
+ });
1218
+ const projectDuplicateJobInputSchema = z.object({
1219
+ projectId: z.string().min(1),
1220
+ name: z.string().min(1),
1221
+ targetWorkspaceId: z.string().min(1),
1222
+ includeSnapshots: z.boolean(),
1223
+ includeAssets: z.boolean()
1224
+ });
1225
+ const applyTemplateJobInputSchema = z.object({
1226
+ templateId: z.string().min(1),
1227
+ templateVersion: z.number().int().min(1),
1228
+ mapping: z.record(z.string(), z.string()).optional()
1229
+ });
1230
+ const templateThumbnailInputSchema = z.object({
1231
+ templateId: z.string().min(1),
1232
+ version: z.number().int().min(1)
1233
+ });
1234
+ const UI_LANGUAGES = [
1235
+ "en",
1236
+ "es",
1237
+ "fr",
1238
+ "de",
1239
+ "pt",
1240
+ "it",
1241
+ "ja",
1242
+ "zh"
1243
+ ];
1244
+ const meUpdateSchema = z.object({
1245
+ displayName: z.string().trim().min(1).max(120).optional(),
1246
+ avatarAssetId: z.string().nullable().optional(),
1247
+ locale: z.string().min(2).max(35).optional(),
1248
+ uiLanguage: z.enum(UI_LANGUAGES).optional(),
1249
+ timeZone: z.string().min(1).max(64).optional()
1250
+ });
1251
+ const USER_PREFERENCES_VERSION = 1;
1252
+ const USER_PREFERENCES_MAX_BYTES = 256 * 1024;
1253
+ const AUTOCORRECT_MAX_PAIRS = 5e3;
1254
+ const RECENT_SEARCHES_MAX = 20;
1255
+ const DEVICE_CLASSES = ["phone", "tablet", "desktop"];
1256
+ const section = z.looseObject({});
1257
+ const appearanceSchema = z.looseObject({
1258
+ theme: z.enum(["system", "light", "dark"]).optional(),
1259
+ pageTheme: z.enum(["paper", "night", "midnight", "sepia", "custom"]).optional(),
1260
+ custom: section.optional(),
1261
+ density: z.string().max(40).optional(),
1262
+ largeIcons: z.boolean().optional(),
1263
+ reducedMotion: z.boolean().optional()
1264
+ });
1265
+ const keymapSchema = z.looseObject({
1266
+ preset: z.enum(["fadewright", "finalDraft", "fadeIn"]).optional(),
1267
+ overrides: z.record(z.string(), z.string().nullable()).optional(),
1268
+ byPlatform: section.optional()
1269
+ });
1270
+ const autocorrectSchema = z.looseObject({
1271
+ options: section.optional(),
1272
+ pairs: z.array(
1273
+ z.object({ from: z.string().min(1).max(200), to: z.string().max(200) })
1274
+ ).max(AUTOCORRECT_MAX_PAIRS).optional()
1275
+ });
1276
+ const languageSchema = z.looseObject({
1277
+ scriptDefault: z.string().max(35).optional(),
1278
+ installedDictionaries: z.array(z.string().max(35)).max(50).optional(),
1279
+ ignoreLists: section.optional(),
1280
+ lookups: z.array(z.object({ label: z.string().max(80), url: z.string().max(500) })).max(50).optional()
1281
+ });
1282
+ const tableReadSchema = z.looseObject({
1283
+ narratorVoiceByLanguage: z.record(z.string(), section).optional(),
1284
+ rate: z.number().optional(),
1285
+ actors: z.array(z.looseObject({ id: z.string(), name: z.string() })).max(200).optional()
1286
+ });
1287
+ const userPreferencesSchema = z.strictObject({
1288
+ version: z.number().int().min(1).max(1e3).default(USER_PREFERENCES_VERSION),
1289
+ appearance: appearanceSchema.optional(),
1290
+ editor: section.optional(),
1291
+ deviceClasses: z.strictObject({
1292
+ phone: section.optional(),
1293
+ tablet: section.optional(),
1294
+ desktop: section.optional()
1295
+ }).optional(),
1296
+ keymap: keymapSchema.optional(),
1297
+ autocorrect: autocorrectSchema.optional(),
1298
+ language: languageSchema.optional(),
1299
+ tableRead: tableReadSchema.optional(),
1300
+ toolbar: section.optional(),
1301
+ recentSearches: z.array(z.string().max(500)).max(RECENT_SEARCHES_MAX).optional(),
1302
+ revisionColourSets: z.array(section).max(100).optional(),
1303
+ offline: section.optional()
1304
+ });
1305
+ const USER_PREFERENCES_KEYS = [
1306
+ "version",
1307
+ "appearance",
1308
+ "editor",
1309
+ "deviceClasses",
1310
+ "keymap",
1311
+ "autocorrect",
1312
+ "language",
1313
+ "tableRead",
1314
+ "toolbar",
1315
+ "recentSearches",
1316
+ "revisionColourSets",
1317
+ "offline"
1318
+ ];
1319
+ const userPreferencesPutSchema = userPreferencesSchema.extend({
1320
+ baseUpdatedAt: z.string().min(1).max(40)
1321
+ });
1322
+ const DICTIONARY_MAX_WORDS = 5e4;
1323
+ const dictionaryWord = z.string().trim().min(1).max(100);
1324
+ const dictionaryUpdateSchema = z.object({
1325
+ add: z.array(dictionaryWord).max(5e3).optional(),
1326
+ remove: z.array(dictionaryWord).max(5e3).optional()
1327
+ });
1328
+ const USER_MACRO_MAX = 500;
1329
+ const MACRO_TRIGGER_KINDS = ["shortcut", "alias"];
1330
+ const macroOptionFields = {
1331
+ smartReplace: z.boolean(),
1332
+ confirm: z.boolean(),
1333
+ wordOnly: z.boolean(),
1334
+ matchCase: z.boolean(),
1335
+ activeInStyles: z.array(z.string().max(80)).max(100)
1336
+ };
1337
+ const macroOptionsSchema = z.object({
1338
+ smartReplace: macroOptionFields.smartReplace.default(false),
1339
+ confirm: macroOptionFields.confirm.default(false),
1340
+ wordOnly: macroOptionFields.wordOnly.default(true),
1341
+ matchCase: macroOptionFields.matchCase.default(false),
1342
+ activeInStyles: macroOptionFields.activeInStyles.default([])
1343
+ });
1344
+ const macroOptionsPatchSchema = z.object(macroOptionFields).partial();
1345
+ const userMacroSchema = z.object({
1346
+ name: z.string().trim().min(1).max(120),
1347
+ trigger: z.object({
1348
+ kind: z.enum(MACRO_TRIGGER_KINDS),
1349
+ value: z.string().trim().min(1).max(80)
1350
+ }),
1351
+ insertText: z.string().max(1e4),
1352
+ setElementStyle: z.string().max(80).nullable().optional(),
1353
+ nextElementStyle: z.string().max(80).nullable().optional(),
1354
+ options: macroOptionsSchema.prefault({})
1355
+ });
1356
+ const userMacroPatchSchema = z.object({
1357
+ name: userMacroSchema.shape.name.optional(),
1358
+ trigger: z.object({
1359
+ kind: z.enum(MACRO_TRIGGER_KINDS).optional(),
1360
+ value: userMacroSchema.shape.trigger.shape.value.optional()
1361
+ }).optional(),
1362
+ insertText: userMacroSchema.shape.insertText.optional(),
1363
+ setElementStyle: userMacroSchema.shape.setElementStyle,
1364
+ nextElementStyle: userMacroSchema.shape.nextElementStyle,
1365
+ options: macroOptionsPatchSchema.optional()
1366
+ });
1367
+ const WRITING_GOAL_KINDS = [
1368
+ "wordsPerDay",
1369
+ "pagesPerDay",
1370
+ "minutesPerDay",
1371
+ "wordsPerWeek",
1372
+ "documentTarget"
1373
+ ];
1374
+ const WRITING_GOALS_MAX = 50;
1375
+ const WRITING_STATS_GRANULARITIES = [
1376
+ "day",
1377
+ "week",
1378
+ "month",
1379
+ "year"
1380
+ ];
1381
+ const isoInstant = z.iso.datetime({ offset: true });
1382
+ const writingSessionSchema = z.object({
1383
+ clientSessionId: z.string().min(8).max(64).regex(/^[A-Za-z0-9_-]+$/),
1384
+ documentId: z.string().min(1).max(100).nullable().optional(),
1385
+ startedAt: isoInstant,
1386
+ endedAt: isoInstant,
1387
+ activeSeconds: z.number().int().min(0).max(86400),
1388
+ wordsAdded: z.number().int().min(0).max(1e6),
1389
+ wordsRemoved: z.number().int().min(0).max(1e6),
1390
+ netPagesEighths: z.number().int().min(-1e5).max(1e5),
1391
+ sprint: z.object({
1392
+ targetWords: z.number().int().min(1).max(1e6).optional(),
1393
+ targetMinutes: z.number().int().min(1).max(1440).optional()
1394
+ }).optional()
1395
+ }).refine((s) => Date.parse(s.endedAt) >= Date.parse(s.startedAt), {
1396
+ message: "endedAt is before startedAt",
1397
+ path: ["endedAt"]
1398
+ });
1399
+ const localDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
1400
+ const writingStatsQuerySchema = z.object({
1401
+ from: localDate.optional(),
1402
+ to: localDate.optional(),
1403
+ granularity: z.enum(WRITING_STATS_GRANULARITIES).default("day"),
1404
+ documentId: z.string().min(1).max(100).optional()
1405
+ });
1406
+ const writingGoalInputSchema = z.object({
1407
+ id: z.string().min(4).max(64).regex(/^[A-Za-z0-9_-]+$/).optional(),
1408
+ kind: z.enum(WRITING_GOAL_KINDS),
1409
+ documentId: z.string().min(1).max(100).nullable().optional(),
1410
+ target: z.number().int().min(1).max(1e7),
1411
+ daysOfWeek: z.array(z.number().int().min(0).max(6)).max(7).optional(),
1412
+ idleTimeoutSec: z.number().int().min(5).max(3600).optional()
1413
+ }).refine((g) => g.kind !== "documentTarget" || !!g.documentId, {
1414
+ message: "documentTarget needs documentId",
1415
+ path: ["documentId"]
1416
+ });
1417
+ const writingGoalsPutSchema = z.object({
1418
+ goals: z.array(writingGoalInputSchema)
1419
+ });
1420
+ const ACCOUNT_DELETE_CONFIRM = "DELETE";
1421
+ const accountDeleteSchema = z.object({
1422
+ confirm: z.literal(ACCOUNT_DELETE_CONFIRM)
1423
+ });
1424
+ const accountExportInputSchema = z.object({}).default({});
1425
+ const ACCOUNT_EXPORT_FILENAME = "fadewright-export.zip";
1426
+ const DOCUMENT_VIEW_STATE_MAX_BYTES = 64 * 1024;
1427
+ const deviceViewSchema = z.looseObject({
1428
+ view: z.string().max(40).optional(),
1429
+ zoom: z.number().optional(),
1430
+ invisibles: z.boolean().optional(),
1431
+ ruler: z.boolean().optional()
1432
+ });
1433
+ const documentViewStateSchema = z.strictObject({
1434
+ navigator: z.looseObject({
1435
+ tabs: z.array(z.looseObject({})).max(50).optional(),
1436
+ activeTab: z.string().max(80).nullable().optional()
1437
+ }).optional(),
1438
+ layout: z.looseObject({
1439
+ split: z.union([z.string().max(40), z.number(), z.null()]).optional(),
1440
+ panes: z.array(z.unknown()).max(20).optional()
1441
+ }).optional(),
1442
+ views: z.strictObject({
1443
+ phone: deviceViewSchema.optional(),
1444
+ tablet: deviceViewSchema.optional(),
1445
+ desktop: deviceViewSchema.optional()
1446
+ }).optional()
1447
+ });
1448
+ const documentViewStatePutSchema = documentViewStateSchema.extend({
1449
+ baseUpdatedAt: z.string().min(1).max(40).nullable().optional()
1450
+ });
1451
+ const templateListQuerySchema = z.object({
1452
+ category: z.string().optional(),
1453
+ workspaceId: z.string().optional(),
1454
+ scope: z.enum(["builtin", "user", "workspace"]).optional()
1455
+ });
1456
+ const templateGetQuerySchema = z.object({
1457
+ version: z.coerce.number().int().min(1).optional()
1458
+ });
1459
+ const templateBody = z.custom(
1460
+ (v) => typeof v === "object" && v !== null && !Array.isArray(v),
1461
+ {
1462
+ message: "template must be an object"
1463
+ }
1464
+ );
1465
+ const templateCreateSchema = z.object({
1466
+ scope: z.enum(["user", "workspace"]),
1467
+ workspaceId: z.string().min(1).optional(),
1468
+ template: templateBody
1469
+ }).refine((v) => v.scope !== "workspace" || !!v.workspaceId, {
1470
+ message: "workspaceId is required for a workspace template",
1471
+ path: ["workspaceId"]
1472
+ });
1473
+ const templateVersionCreateSchema = z.object({ template: templateBody });
1474
+ const templateUpdateSchema = z.object({
1475
+ name: z.string().trim().min(1).max(120).optional(),
1476
+ description: z.string().max(2e3).nullable().optional(),
1477
+ category: z.string().min(1).max(40).optional()
1478
+ });
1479
+ const TEMPLATE_FILE_FORMATS = [
1480
+ "fwtemplate",
1481
+ "fadein-template",
1482
+ "fdxt"
1483
+ ];
1484
+ const FWTEMPLATE_FORMAT = "fadewright-template";
1485
+ const FWTEMPLATE_FORMAT_VERSION = 1;
1486
+ const templateImportSchema = z.object({
1487
+ scope: z.enum(["user", "workspace"]),
1488
+ workspaceId: z.string().min(1).optional(),
1489
+ filename: z.string().min(1).max(255),
1490
+ contentB64: z.string().min(1)
1491
+ }).refine((v) => v.scope !== "workspace" || !!v.workspaceId, {
1492
+ message: "workspaceId is required for a workspace template",
1493
+ path: ["workspaceId"]
1494
+ });
1495
+ const templateImportByIdSchema = z.object({
1496
+ importId: z.string().min(1)
1497
+ });
1498
+ const templateExportQuerySchema = z.object({
1499
+ format: z.enum(["fwtemplate", "json", "fadein-template", "fdxt"]).default("fwtemplate"),
1500
+ version: z.coerce.number().int().min(1).optional()
1501
+ });
1502
+ const SNAPSHOT_AUTO_REASONS = [
1503
+ "pre-ai-polish",
1504
+ "pre-import",
1505
+ "pre-template",
1506
+ "pre-open",
1507
+ "pre-restore",
1508
+ "pre-cherry-pick-bulk",
1509
+ "pre-clear-revisions",
1510
+ "pre-delete-revision-set",
1511
+ "pre-unlock-all-pages",
1512
+ "pre-bulk-change-decision",
1513
+ "pre-account-transfer",
1514
+ "offline-edits",
1515
+ "access-revoked"
1516
+ ];
1517
+ const CLIENT_AUTO_REASONS = ["offline-edits", "access-revoked"];
1518
+ const SNAPSHOT_KINDS = ["manual", "auto"];
1519
+ const VERSION_REASONS = [
1520
+ "interval",
1521
+ "session-end",
1522
+ "pre-epoch",
1523
+ "pre-restore",
1524
+ "daily",
1525
+ "reconnect"
1526
+ ];
1527
+ const snapshotCreateSchema = z.object({
1528
+ /** Idempotency key, unique per document. */
1529
+ clientSnapshotId: z.string().min(1).max(64).optional(),
1530
+ name: z.string().trim().min(1).max(120),
1531
+ note: z.string().max(2e3).optional(),
1532
+ kind: z.enum(SNAPSHOT_KINDS).optional(),
1533
+ autoReason: z.enum(SNAPSHOT_AUTO_REASONS).optional(),
1534
+ sourceEpoch: z.number().int().min(0).optional(),
1535
+ assumedParentId: z.string().nullable().optional(),
1536
+ createdAt: z.string().optional(),
1537
+ createdOnDevice: z.string().max(200).optional(),
1538
+ /**
1539
+ * Client-provided state for offline creation (spec 03 §10.2, spec 05 §6.11): exactly what the writer saw, uploaded through
1540
+ * `POST /uploads/state`. With it, `sourceEpoch` may be older than the live epoch (an offline-edits snapshot after an epoch bump).
1541
+ */
1542
+ state: z.object({ uploadKey: z.string().min(1).max(128) }).optional()
1543
+ });
1544
+ const snapshotOpenSchema = z.object({
1545
+ confirm: z.literal(true),
1546
+ dryRun: z.boolean().optional()
1547
+ });
1548
+ const snapshotForkSchema = z.object({
1549
+ title: z.string().trim().min(1).max(200),
1550
+ targetProjectId: z.string().optional()
1551
+ });
1552
+ const sourceRefSchema = z.discriminatedUnion("kind", [
1553
+ z.object({ kind: z.literal("live"), documentId: z.string() }),
1554
+ z.object({ kind: z.literal("snapshot"), snapshotId: z.string() }),
1555
+ z.object({
1556
+ kind: z.literal("version"),
1557
+ documentId: z.string(),
1558
+ versionId: z.string()
1559
+ })
1560
+ ]);
1561
+ const versionRestoreAsCopySchema = z.object({
1562
+ title: z.string().trim().min(1).max(200),
1563
+ targetProjectId: z.string().optional()
1564
+ });
1565
+ const snapshotPrefsSchema = z.object({ hidden: z.boolean() });
1566
+ const SNAPSHOT_NOTES_MAX = 200;
1567
+ const snapshotNoteCreateSchema = z.object({
1568
+ body: z.string().trim().min(1).max(2e3)
1569
+ });
1570
+ const snapshotCommentAnchorSchema = z.object({
1571
+ elementId: z.string().min(1),
1572
+ offset: z.number().int().min(0),
1573
+ length: z.number().int().min(0)
1574
+ });
1575
+ const snapshotCommentCreateSchema = z.object({
1576
+ anchor: snapshotCommentAnchorSchema,
1577
+ body: z.string().trim().min(1).max(1e4),
1578
+ /** User ids mentioned in the body (notifications are B12). */
1579
+ mentions: z.array(z.string().min(1)).max(50).optional()
1580
+ });
1581
+ const snapshotCommentResolveSchema = z.object({ resolved: z.boolean() });
1582
+ const snapshotCommentsQuerySchema = cursorQuerySchema;
1583
+ const COMPARE_GRANULARITIES = ["scene", "element", "word"];
1584
+ const TRACKED_CHANGE_MODES = [
1585
+ "accept-all",
1586
+ "reject-all",
1587
+ "as-marked"
1588
+ ];
1589
+ const compareRequestSchema = z.object({
1590
+ base: sourceRefSchema,
1591
+ target: sourceRefSchema,
1592
+ granularity: z.enum(COMPARE_GRANULARITIES).default("element"),
1593
+ ignoreFormatting: z.boolean().default(false),
1594
+ ignoreRevisionMarks: z.boolean().default(false),
1595
+ ignoreTrackedChanges: z.enum(TRACKED_CHANGE_MODES).default("as-marked")
1596
+ });
1597
+ const COMPARE_MAX_PAGES = 400;
1598
+ const elementHistoryQuerySchema = cursorQuerySchema;
1599
+ const COMMAND_BATCH_MAX = 500;
1600
+ const commandBatchRequestSchema = z.object({
1601
+ commands: z.array(z.record(z.string(), z.unknown())).min(1).max(COMMAND_BATCH_MAX),
1602
+ baseEpoch: z.number().int().min(0),
1603
+ expectedHashes: z.record(z.string(), z.string()).optional(),
1604
+ dryRun: z.boolean().optional()
1605
+ });
1606
+ const SCENE_BATCH_MAX = 20;
1607
+ const ELEMENT_BATCH_MAX = 500;
1608
+ const elementsBatchRequestSchema = z.object({
1609
+ elementIds: z.array(z.string().min(1)).min(1).max(ELEMENT_BATCH_MAX)
1610
+ });
1611
+ const scenesBatchRequestSchema = z.object({
1612
+ sceneIds: z.array(z.string().min(1)).min(1).max(SCENE_BATCH_MAX)
1613
+ });
1614
+ const IMPORT_FORMAT_IDS = ["fountain", "fdx", "fadein"];
1615
+ const EXPORT_FORMAT_IDS = ["fountain", "fdx", "json"];
1616
+ const IMPORT_MAX_BYTES_DEFAULT = 100 * 1024 * 1024;
1617
+ const documentImportSchema = z.object({
1618
+ /** Client-generated `doc_...`; import is idempotent on it. */
1619
+ id: z.string().startsWith("doc_").optional(),
1620
+ /** Only a hint: the format is detected from the content. */
1621
+ filename: z.string().min(1).max(500),
1622
+ /** Base64 of the file bytes. */
1623
+ contentB64: z.string(),
1624
+ /** Defaults to the file's title page title, then the filename. */
1625
+ title: z.string().trim().min(1).max(200).optional(),
1626
+ kind: documentKindSchema.optional(),
1627
+ templateId: z.string().optional(),
1628
+ language: z.string().max(16).optional()
1629
+ });
1630
+ const documentExportSchema = z.object({
1631
+ format: z.string().min(1).max(32)
1632
+ });
1633
+ const sourceParamSchema = z.string().regex(
1634
+ /^(live|snapshot:[A-Za-z0-9_-]+|version:[A-Za-z0-9_-]+)$/,
1635
+ "source must be live, snapshot:<id> or version:<id>"
1636
+ );
1637
+ const docSourceSchema = z.discriminatedUnion("kind", [
1638
+ z.object({ kind: z.literal("live") }),
1639
+ z.object({ kind: z.literal("snapshot"), snapshotId: z.string().min(1) }),
1640
+ z.object({ kind: z.literal("version"), versionId: z.string().min(1) })
1641
+ ]);
1642
+ const sourceQuerySchema = z.object({
1643
+ source: sourceParamSchema.optional()
1644
+ });
1645
+ function sourceParamOf(ref) {
1646
+ if (!ref || ref.kind === "live") return void 0;
1647
+ return ref.kind === "snapshot" ? `snapshot:${ref.snapshotId}` : `version:${ref.versionId}`;
1648
+ }
1649
+ const entityListQuerySchema = cursorQuerySchema.extend({
1650
+ kind: z.string().min(1).optional(),
1651
+ q: z.string().min(1).max(200).optional(),
1652
+ source: sourceParamSchema.optional()
1653
+ });
1654
+ const entityUsageQuerySchema = z.object({
1655
+ variantId: z.string().min(1).optional(),
1656
+ source: sourceParamSchema.optional()
1657
+ });
1658
+ const entityDialogueQuerySchema = z.object({
1659
+ sceneIds: z.string().optional(),
1660
+ source: sourceParamSchema.optional()
1661
+ });
1662
+ const tagsQuerySchema = z.object({
1663
+ sceneId: z.string().optional(),
1664
+ categoryId: z.string().optional(),
1665
+ entityId: z.string().optional(),
1666
+ source: sourceParamSchema.optional()
1667
+ });
1668
+ const notesQuerySchema = z.object({
1669
+ sceneId: z.string().optional(),
1670
+ type: z.string().optional(),
1671
+ status: z.enum(["open", "resolved"]).optional(),
1672
+ source: sourceParamSchema.optional()
1673
+ });
1674
+ const changesQuerySchema = z.object({
1675
+ authorId: z.string().optional(),
1676
+ sceneId: z.string().optional(),
1677
+ source: sourceParamSchema.optional()
1678
+ });
1679
+ const fountainQuerySchema = z.object({
1680
+ sceneIds: z.string().optional(),
1681
+ fromSceneId: z.string().optional(),
1682
+ source: sourceParamSchema.optional()
1683
+ });
1684
+ const FOUNTAIN_MAX_BYTES = 1e6;
1685
+ const LOCATOR_KINDS = [
1686
+ "scene",
1687
+ "element",
1688
+ "shot",
1689
+ "entity",
1690
+ "page"
1691
+ ];
1692
+ const RESOLVE_BATCH_MAX = 200;
1693
+ const resolveBatchSchema = z.object({
1694
+ locators: z.array(z.string().min(1).max(500)).min(1).max(RESOLVE_BATCH_MAX),
1695
+ snapshotId: z.string().min(1).optional()
1696
+ });
1697
+ const resolveOneQuerySchema = z.object({
1698
+ ref: z.string().min(1).max(500),
1699
+ snapshotId: z.string().min(1).optional()
1700
+ });
1701
+ const SEARCH_HIT_TYPES = [
1702
+ "document",
1703
+ "scene",
1704
+ "element",
1705
+ "entity",
1706
+ "note",
1707
+ "asset"
1708
+ ];
1709
+ const csv = (v) => typeof v === "string" ? v.split(",").map((s) => s.trim()).filter(Boolean) : v;
1710
+ const searchQuerySchema = cursorQuerySchema.extend({
1711
+ q: z.string().min(1).max(200),
1712
+ workspaceId: z.string().min(1).optional(),
1713
+ projectId: z.string().min(1).optional(),
1714
+ documentId: z.string().min(1).optional(),
1715
+ types: z.preprocess(csv, z.array(z.enum(SEARCH_HIT_TYPES)).min(1)).optional(),
1716
+ styleIds: z.preprocess(csv, z.array(z.string()).min(1)).optional(),
1717
+ characterId: z.string().min(1).optional(),
1718
+ language: z.string().min(2).max(35).optional()
1719
+ });
1720
+ const documentSearchQuerySchema = z.object({
1721
+ q: z.string().min(1).max(500),
1722
+ mode: z.enum(["text", "regex"]).default("text"),
1723
+ styles: z.preprocess(csv, z.array(z.string()).min(1)).optional(),
1724
+ characters: z.preprocess(csv, z.array(z.string()).min(1)).optional(),
1725
+ limit: z.coerce.number().int().min(1).max(100).default(50),
1726
+ source: sourceParamSchema.optional()
1727
+ });
1728
+ const JOB_IMPORT_FORMATS = ["fountain", "fdx", "fadein"];
1729
+ const JOB_EXPORT_FORMATS = ["fountain", "fdx", "json"];
1730
+ const COMBINED_EXPORT_FORMATS = ["fdx", "fountain"];
1731
+ const COMBINED_EXPORT_MAX_DOCUMENTS = 20;
1732
+ const BATCH_WATERMARK_MAX_RECIPIENTS = 500;
1733
+ const UPLOAD_PURPOSES = ["state", "watermark_lookup", "import"];
1734
+ const sha256HexSchema$1 = z.string().regex(/^[0-9a-fA-F]{64}$/, "sha256Hex must be 64 hex characters");
1735
+ const uploadStateRequestSchema = z.object({
1736
+ sizeBytes: z.number().int().min(1),
1737
+ sha256Hex: sha256HexSchema$1,
1738
+ purpose: z.enum(["state", "watermark_lookup"]).default("state")
1739
+ });
1740
+ const importOptionsSchema = z.object({
1741
+ templateId: z.string().min(1).optional(),
1742
+ /** `auto`: OCR a PDF without a text layer; `force`; `off`. No OCR exists yet: `force` is `OCR_UNAVAILABLE`. */
1743
+ ocr: z.enum(["auto", "force", "off"]).default("auto"),
1744
+ ocrLanguage: z.string().min(2).max(16).optional(),
1745
+ title: z.string().trim().min(1).max(200).optional(),
1746
+ kind: documentKindSchema.optional(),
1747
+ language: z.string().max(16).optional()
1748
+ });
1749
+ const importFileFields = {
1750
+ filename: z.string().min(1).max(500),
1751
+ sizeBytes: z.number().int().min(1),
1752
+ sha256Hex: sha256HexSchema$1,
1753
+ /** Auto-detected from the uploaded bytes when omitted. */
1754
+ format: z.string().min(1).max(32).optional(),
1755
+ options: importOptionsSchema.optional()
1756
+ };
1757
+ const importCreateSchema = z.object({
1758
+ targetProjectId: z.string().min(1).optional(),
1759
+ templateTarget: z.object({
1760
+ scope: z.enum(["user", "workspace"]),
1761
+ workspaceId: z.string().min(1).optional()
1762
+ }).refine((v) => v.scope !== "workspace" || !!v.workspaceId, {
1763
+ message: "workspaceId is required for a workspace template",
1764
+ path: ["workspaceId"]
1765
+ }).optional(),
1766
+ ...importFileFields
1767
+ }).refine(
1768
+ (v) => (v.targetProjectId ? 1 : 0) + (v.templateTarget ? 1 : 0) === 1,
1769
+ {
1770
+ message: "Send exactly one of targetProjectId and templateTarget",
1771
+ path: ["targetProjectId"]
1772
+ }
1773
+ );
1774
+ const importOverSchema = z.object(importFileFields);
1775
+ const importJobInputSchema = z.object({
1776
+ importId: z.string().min(1),
1777
+ targetProjectId: z.string().min(1),
1778
+ newDocumentId: z.string().min(1),
1779
+ filename: z.string().min(1).max(500),
1780
+ options: importOptionsSchema.optional()
1781
+ });
1782
+ const importOverJobInputSchema = z.object({
1783
+ importId: z.string().min(1),
1784
+ documentId: z.string().min(1),
1785
+ filename: z.string().min(1).max(500),
1786
+ options: importOptionsSchema.optional()
1787
+ });
1788
+ const CONVERSION_REPORT_OUTPUT = "conversion-report.json";
1789
+ const watermarkStyleSchema = z.object({
1790
+ text: z.string().min(1).max(200).default("{recipient}"),
1791
+ opacity: z.number().min(0.05).max(1).optional(),
1792
+ position: z.enum(["diagonalAscending", "diagonalDescending", "horizontal"]).optional()
1793
+ });
1794
+ const batchWatermarkSchema = z.object({
1795
+ recipients: z.array(
1796
+ z.object({
1797
+ name: z.string().trim().min(1).max(200),
1798
+ email: z.string().trim().min(1).max(320).optional(),
1799
+ company: z.string().trim().min(1).max(200).optional()
1800
+ })
1801
+ ).min(1).max(BATCH_WATERMARK_MAX_RECIPIENTS),
1802
+ visible: watermarkStyleSchema.optional(),
1803
+ /** Forensic mark. Only a PDF writer can embed one; text formats carry the export code as a visible note (see the API notes). */
1804
+ invisible: z.boolean().default(false),
1805
+ /** `email` needs the notification slice (B12): refused with `VALIDATION` until then. */
1806
+ deliver: z.enum(["download", "email"]).default("download"),
1807
+ emailMessage: z.string().max(2e3).optional()
1808
+ });
1809
+ const exportOptionsSchema = z.object({
1810
+ /** Download name without extension (default: the document title). */
1811
+ filename: z.string().trim().min(1).max(120).optional(),
1812
+ batchWatermark: batchWatermarkSchema.optional()
1813
+ });
1814
+ const exportCreateSchema = z.object({
1815
+ format: z.string().min(1).max(32),
1816
+ source: docSourceSchema.optional(),
1817
+ options: exportOptionsSchema.optional()
1818
+ });
1819
+ const exportCombinedSchema = z.object({
1820
+ documentIds: z.array(z.string().min(1)).min(1).max(COMBINED_EXPORT_MAX_DOCUMENTS),
1821
+ format: z.string().min(1).max(32),
1822
+ options: exportOptionsSchema.omit({ batchWatermark: true }).optional()
1823
+ });
1824
+ const exportJobInputSchema = z.object({
1825
+ format: z.enum(JOB_EXPORT_FORMATS),
1826
+ documentIds: z.array(z.string().min(1)).max(COMBINED_EXPORT_MAX_DOCUMENTS).optional(),
1827
+ source: docSourceSchema.optional(),
1828
+ options: exportOptionsSchema.optional()
1829
+ });
1830
+ const EXPORT_CODE_PREFIX = "FWX-";
1831
+ const watermarkLookupSchema = z.object({
1832
+ exportId: z.string().trim().min(8).max(64).optional(),
1833
+ pdfUploadKey: z.string().min(1).max(100).optional()
1834
+ }).refine((v) => (v.exportId ? 1 : 0) + (v.pdfUploadKey ? 1 : 0) === 1, {
1835
+ message: "Send exactly one of exportId and pdfUploadKey",
1836
+ path: ["exportId"]
1837
+ });
1838
+ const AI_TASKS = ["coverage", "polish.dialogue"];
1839
+ const aiTaskSchema = z.enum(AI_TASKS);
1840
+ const AI_ENDPOINTS = [
1841
+ "coverage-review",
1842
+ "polish",
1843
+ "summarize-chunk"
1844
+ ];
1845
+ const AI_JOB_STATUSES = [
1846
+ "queued",
1847
+ "running",
1848
+ "succeeded",
1849
+ "failed",
1850
+ "cancelled"
1851
+ ];
1852
+ const AI_LIMITS = {
1853
+ noteTitleMax: 80,
1854
+ noteBodyMax: 1200,
1855
+ noteAnchorsMax: 8,
1856
+ strengthsMax: 8,
1857
+ weaknessesMax: 12,
1858
+ sceneNotesMax: 20,
1859
+ loglineMax: 300,
1860
+ summaryMax: 4e3,
1861
+ genreMax: 3,
1862
+ verdictRationaleMax: 800,
1863
+ polishSuggestionsMax: 200,
1864
+ polishTextMax: 4e3,
1865
+ rationaleMax: 400,
1866
+ chunkSummaryMax: 1200,
1867
+ sceneSummaryMax: 280,
1868
+ polishMaxScenes: 8,
1869
+ instructionsMax: 500
1870
+ };
1871
+ const REVIEW_CATEGORIES = [
1872
+ "premise",
1873
+ "structure",
1874
+ "character",
1875
+ "dialogue",
1876
+ "pacing",
1877
+ "tone",
1878
+ "theme",
1879
+ "worldbuilding",
1880
+ "format",
1881
+ "marketability",
1882
+ "clarity",
1883
+ "continuity"
1884
+ ];
1885
+ const SEVERITIES = ["praise", "minor", "moderate", "major"];
1886
+ const COVERAGE_VERDICTS = ["recommend", "consider", "pass"];
1887
+ const anchorRefSchema = z.object({
1888
+ sceneId: z.string(),
1889
+ elementId: z.string().nullable()
1890
+ });
1891
+ const noteSchema = z.object({
1892
+ category: z.enum(REVIEW_CATEGORIES),
1893
+ severity: z.enum(SEVERITIES),
1894
+ title: z.string().min(1).max(AI_LIMITS.noteTitleMax),
1895
+ body: z.string().min(1).max(AI_LIMITS.noteBodyMax),
1896
+ anchors: z.array(anchorRefSchema).max(AI_LIMITS.noteAnchorsMax)
1897
+ });
1898
+ const coverageResponseSchema = z.object({
1899
+ logline: z.string().max(AI_LIMITS.loglineMax),
1900
+ summary: z.string().max(AI_LIMITS.summaryMax),
1901
+ genre: z.array(z.string()).max(AI_LIMITS.genreMax),
1902
+ strengths: z.array(noteSchema).max(AI_LIMITS.strengthsMax),
1903
+ weaknesses: z.array(noteSchema).max(AI_LIMITS.weaknessesMax),
1904
+ /** Notes about specific scenes: each must anchor at least one scene. */
1905
+ sceneNotes: z.array(noteSchema).max(AI_LIMITS.sceneNotesMax),
1906
+ verdict: z.enum(COVERAGE_VERDICTS),
1907
+ verdictRationale: z.string().max(AI_LIMITS.verdictRationaleMax)
1908
+ });
1909
+ const polishResponseSchema = z.object({
1910
+ suggestions: z.array(
1911
+ z.object({
1912
+ elementId: z.string(),
1913
+ after: z.string().max(AI_LIMITS.polishTextMax),
1914
+ rationale: z.string().max(AI_LIMITS.rationaleMax)
1915
+ })
1916
+ ).max(AI_LIMITS.polishSuggestionsMax)
1917
+ });
1918
+ const chunkSummaryResponseSchema = z.object({
1919
+ scenes: z.array(
1920
+ z.object({
1921
+ sceneId: z.string(),
1922
+ summary: z.string().max(AI_LIMITS.sceneSummaryMax),
1923
+ intensity: z.number().int().min(1).max(5)
1924
+ })
1925
+ ),
1926
+ chunkSummary: z.string().max(AI_LIMITS.chunkSummaryMax)
1927
+ });
1928
+ const aiEndpointInputSchema = z.object({
1929
+ context: z.string(),
1930
+ brief: z.string(),
1931
+ max_output_tokens: z.number().int().positive(),
1932
+ locale: z.string(),
1933
+ script_language: z.string()
1934
+ });
1935
+ const AI_ENDPOINT_OUTPUT_SCHEMAS = {
1936
+ "coverage-review": coverageResponseSchema,
1937
+ polish: polishResponseSchema,
1938
+ "summarize-chunk": chunkSummaryResponseSchema
1939
+ };
1940
+ const aiScopeSchema = z.object({
1941
+ sceneIds: z.array(z.string().min(1)).max(200).optional(),
1942
+ elementIds: z.array(z.string().min(1)).max(2e3).optional()
1943
+ });
1944
+ const aiJobOptionsSchema = z.object({
1945
+ /** Writer's own request, fenced separately from the script. Fixture failure markers only work in test mode. */
1946
+ instructions: z.string().max(AI_LIMITS.instructionsMax).optional(),
1947
+ intensity: z.enum(["light", "medium", "bold"]).optional(),
1948
+ locale: z.string().min(2).max(20).optional()
1949
+ });
1950
+ const aiJobCreateSchema = z.object({
1951
+ task: aiTaskSchema,
1952
+ scope: aiScopeSchema.optional(),
1953
+ options: aiJobOptionsSchema.optional()
1954
+ });
1955
+ const SUGGESTION_STATUSES = [
1956
+ "pending",
1957
+ "accepted",
1958
+ "rejected",
1959
+ "stale"
1960
+ ];
1961
+ const SUGGESTION_SET_STATUSES = [
1962
+ "pending",
1963
+ "partiallyAccepted",
1964
+ "accepted",
1965
+ "rejected",
1966
+ "stale",
1967
+ "expired"
1968
+ ];
1969
+ const suggestionAcceptSchema = z.object({
1970
+ suggestionIds: z.array(z.string().min(1)).min(1).max(200)
1971
+ });
1972
+ const suggestionRejectSchema = z.object({
1973
+ suggestionIds: z.array(z.string().min(1)).min(1).max(200).optional()
1974
+ });
1975
+ const AI_CONSENT_VERSION = 1;
1976
+ const aiConsentAcceptSchema = z.object({ version: z.number().int() });
1977
+ const aiEstimateRequestSchema = z.object({
1978
+ task: aiTaskSchema,
1979
+ scope: z.object({
1980
+ sceneIds: z.array(z.string()).optional(),
1981
+ elementIds: z.array(z.string()).optional()
1982
+ }).optional(),
1983
+ options: z.record(z.string(), z.unknown()).optional()
1984
+ });
1985
+ const suggestionDecideSchema = z.object({
1986
+ accept: z.union([z.array(z.string().min(1)), z.literal("allPending")]),
1987
+ reject: z.union([z.array(z.string().min(1)), z.literal("allPending")]),
1988
+ expectedHashes: z.record(z.string(), z.string()).optional(),
1989
+ dryRun: z.boolean().optional()
1990
+ });
1991
+ const JOB_STATUSES = [
1992
+ "queued",
1993
+ "running",
1994
+ "succeeded",
1995
+ "failed",
1996
+ "cancelled"
1997
+ ];
1998
+ const TERMINAL_JOB_STATUSES = [
1999
+ "succeeded",
2000
+ "failed",
2001
+ "cancelled"
2002
+ ];
2003
+ const isTerminalJobStatus = (s) => TERMINAL_JOB_STATUSES.includes(s);
2004
+ const JOB_LANES = [
2005
+ "ai",
2006
+ "interactive",
2007
+ "bulk",
2008
+ "system",
2009
+ "provider"
2010
+ ];
2011
+ const JOB_KINDS = [
2012
+ "ai.review",
2013
+ "ai.polish",
2014
+ "ai.describe",
2015
+ "export.pdf",
2016
+ "export.fdx",
2017
+ "export.fountain",
2018
+ "export.fadein",
2019
+ "export.fadewright",
2020
+ "export.json",
2021
+ "export.docx",
2022
+ "watermark.batch",
2023
+ "import.fdx",
2024
+ "import.fountain",
2025
+ "import.fadein",
2026
+ "import.pdf",
2027
+ "import.ocr",
2028
+ "import.celtx",
2029
+ "import.scrivener",
2030
+ "import.fdxt",
2031
+ "report.render",
2032
+ "doc.applyTemplate",
2033
+ "doc.importOver",
2034
+ "doc.snapshotOpen",
2035
+ "doc.versionRestore",
2036
+ "doc.compare",
2037
+ "doc.normalize",
2038
+ "doc.renormalize",
2039
+ "doc.compact",
2040
+ "snapshot.project",
2041
+ "project.duplicate",
2042
+ "asset.derive",
2043
+ "account.export",
2044
+ "system.emailSend",
2045
+ "system.emailDigest",
2046
+ "system.writingReminder",
2047
+ "system.pushSend",
2048
+ "system.purge",
2049
+ "system.retentionSweep",
2050
+ "template.thumbnail",
2051
+ // reserved for media adapters (spec 11 §6.2): registered, seeded disabled
2052
+ "image.generate",
2053
+ "image.edit",
2054
+ "image.upscale",
2055
+ "image.turnaround",
2056
+ "video.generate",
2057
+ "video.extend",
2058
+ "video.upscale",
2059
+ "video.lipSync",
2060
+ "voice.tts",
2061
+ "voice.cloneProfile",
2062
+ "voice.change",
2063
+ "music.generate",
2064
+ "sfx.generate",
2065
+ "audio.isolate",
2066
+ "model3d.generate",
2067
+ "model3d.convert",
2068
+ "model3d.rig",
2069
+ "storyboard.generate",
2070
+ "animatic.assemble",
2071
+ "timeline.export",
2072
+ // framework tests
2073
+ "test.echo",
2074
+ "test.flaky"
2075
+ ];
2076
+ const jobKindSchema = z.enum(JOB_KINDS);
2077
+ function aiKindForTask(task) {
2078
+ return task.startsWith("polish") ? "ai.polish" : "ai.review";
2079
+ }
2080
+ const isAiKind = (kind) => kind.startsWith("ai.");
2081
+ const jobSourceSchema = z.object({
2082
+ documentId: z.string().min(1),
2083
+ snapshotId: z.string().min(1).optional(),
2084
+ targetKind: z.string().min(1).max(40),
2085
+ targetId: z.string().min(1).max(100)
2086
+ });
2087
+ const jobCreateSchema = z.object({
2088
+ kind: jobKindSchema,
2089
+ input: z.record(z.string(), z.unknown()).default({}),
2090
+ sources: z.array(jobSourceSchema).max(20).default([]),
2091
+ idempotencyKey: z.string().min(1).max(128).optional(),
2092
+ maxCredits: z.number().int().min(0).optional(),
2093
+ dryRun: z.boolean().optional()
2094
+ });
2095
+ const jobListQuerySchema = cursorQuerySchema.extend({
2096
+ documentId: z.string().min(1).optional(),
2097
+ kind: z.string().regex(/^[A-Za-z0-9]+(\.[A-Za-z0-9]*)?$/).optional(),
2098
+ status: z.enum(JOB_STATUSES).optional()
2099
+ });
2100
+ const JOB_WAIT_MAX_S = 120;
2101
+ const jobGetQuerySchema = z.object({
2102
+ wait: z.coerce.number().int().min(0).max(JOB_WAIT_MAX_S).optional()
2103
+ });
2104
+ const JOB_WEBHOOK_SIGNATURE_HEADER = "X-Webhook-Signature";
2105
+ const IDEMPOTENCY_HEADER = "Idempotency-Key";
2106
+ const IDEMPOTENT_REPLAY_HEADER = "Idempotent-Replayed";
2107
+ const idempotencyKeySchema = z.string().min(1).max(128);
2108
+ const REPORT_KINDS = [
2109
+ "scene",
2110
+ "location",
2111
+ "character",
2112
+ "cast",
2113
+ "dialogue",
2114
+ "script",
2115
+ "text",
2116
+ "notes",
2117
+ "synopsis",
2118
+ "revisions",
2119
+ "tags",
2120
+ "statistics",
2121
+ "inclusivity",
2122
+ "presentProgressive",
2123
+ "dayOutOfDays",
2124
+ "shotList",
2125
+ "structure",
2126
+ "characterArcs"
2127
+ ];
2128
+ const REPORT_FORMATS = ["json", "csv", "pdf", "html"];
2129
+ const REPORT_SYNC_MAX_PAGES = 200;
2130
+ const col = (id, label, align) => ({
2131
+ id,
2132
+ label,
2133
+ ...align ? { align } : {}
2134
+ });
2135
+ const bool = (name, def, description) => ({
2136
+ name,
2137
+ type: "boolean",
2138
+ default: def,
2139
+ ...{}
2140
+ });
2141
+ const list = (name, description) => ({
2142
+ name,
2143
+ type: "stringList",
2144
+ default: [],
2145
+ ...description ? { description } : {}
2146
+ });
2147
+ const en = (name, values, def) => ({ name, type: "enum", values, default: def });
2148
+ const REPORT_DEFS = {
2149
+ scene: {
2150
+ featureId: "F-RPT-001",
2151
+ title: "Scene report",
2152
+ options: [
2153
+ en(
2154
+ "sort",
2155
+ ["scriptOrder", "sceneNumber", "location", "scriptDay"],
2156
+ "scriptOrder"
2157
+ ),
2158
+ bool("includeOmitted", false)
2159
+ ],
2160
+ columns: [
2161
+ col("number", "Scene"),
2162
+ col("intro", "Intro"),
2163
+ col("location", "Location"),
2164
+ col("time", "Time"),
2165
+ col("heading", "Heading"),
2166
+ col("page", "Page"),
2167
+ col("length", "Length"),
2168
+ col("estTime", "Est. time", "right"),
2169
+ col("synopsis", "Synopsis"),
2170
+ col("colour", "Colour"),
2171
+ col("speaking", "Speaking"),
2172
+ col("nonSpeaking", "Non-speaking"),
2173
+ col("scriptDay", "Script day")
2174
+ ]
2175
+ },
2176
+ location: {
2177
+ featureId: "F-RPT-002",
2178
+ title: "Location report",
2179
+ options: [en("sort", ["name", "firstAppearance", "length"], "name")],
2180
+ columns: [
2181
+ col("location", "Location"),
2182
+ col("intExt", "INT/EXT"),
2183
+ col("times", "Times of day"),
2184
+ col("scenes", "Scenes"),
2185
+ col("pages", "Pages"),
2186
+ col("eighths", "Length"),
2187
+ col("sceneCount", "Scene count", "right")
2188
+ ]
2189
+ },
2190
+ character: {
2191
+ featureId: "F-RPT-003",
2192
+ title: "Character report",
2193
+ options: [
2194
+ list("characters", "Entity ids or names; empty means every character"),
2195
+ bool("includeNonSpeaking", false),
2196
+ {
2197
+ name: "monologueWords",
2198
+ type: "number",
2199
+ default: 100,
2200
+ min: 1,
2201
+ max: 1e4
2202
+ },
2203
+ bool("includeDialogue", false)
2204
+ ],
2205
+ columns: [
2206
+ col("character", "Character"),
2207
+ col("scenes", "Speaking scenes"),
2208
+ col("nonSpeakingScenes", "Non-speaking scenes"),
2209
+ col("extensions", "Extensions"),
2210
+ col("parentheticals", "Parentheticals", "right"),
2211
+ col("monologues", "Monologues", "right"),
2212
+ col("dialogue", "Dialogue")
2213
+ ]
2214
+ },
2215
+ cast: {
2216
+ featureId: "F-RPT-004",
2217
+ title: "Cast report",
2218
+ options: [
2219
+ en(
2220
+ "sort",
2221
+ ["alphabetical", "scriptOrder", "dialogueCount", "words"],
2222
+ "alphabetical"
2223
+ )
2224
+ ],
2225
+ columns: [
2226
+ col("character", "Character"),
2227
+ col("dialogues", "Dialogues", "right"),
2228
+ col("words", "Words", "right"),
2229
+ col("lines", "Lines", "right"),
2230
+ col("speakingScenes", "Speaking scenes", "right"),
2231
+ col("totalScenes", "Total scenes", "right"),
2232
+ col("pagesPresent", "Pages present"),
2233
+ col("firstAppearance", "First appearance"),
2234
+ col("gender", "Gender"),
2235
+ col("role", "Role"),
2236
+ col("age", "Age")
2237
+ ]
2238
+ },
2239
+ dialogue: {
2240
+ featureId: "F-RPT-005",
2241
+ title: "Dialogue report",
2242
+ options: [
2243
+ list("characters", "Entity ids or names; empty means every character"),
2244
+ bool("includeParentheticals", false)
2245
+ ],
2246
+ columns: [
2247
+ col("number", "#", "right"),
2248
+ col("scene", "Scene"),
2249
+ col("page", "Page"),
2250
+ col("character", "Character"),
2251
+ col("text", "Dialogue")
2252
+ ]
2253
+ },
2254
+ script: {
2255
+ featureId: "F-RPT-006",
2256
+ title: "Script report",
2257
+ options: [list("styles", "Style ids or roles; empty means every element")],
2258
+ columns: [
2259
+ col("page", "Page"),
2260
+ col("scene", "Scene"),
2261
+ col("style", "Style"),
2262
+ col("text", "Text")
2263
+ ]
2264
+ },
2265
+ text: {
2266
+ featureId: "F-RPT-007",
2267
+ title: "Text report",
2268
+ options: [
2269
+ {
2270
+ name: "q",
2271
+ type: "string",
2272
+ required: true,
2273
+ description: "Text to find"
2274
+ },
2275
+ en("mode", ["text", "regex"], "text"),
2276
+ list("styles", "Restrict to these style ids")
2277
+ ],
2278
+ columns: [
2279
+ col("page", "Page"),
2280
+ col("scene", "Scene"),
2281
+ col("style", "Style"),
2282
+ col("snippet", "Context")
2283
+ ]
2284
+ },
2285
+ notes: {
2286
+ featureId: "F-RPT-008",
2287
+ title: "Notes report",
2288
+ options: [
2289
+ list("types"),
2290
+ list("authors"),
2291
+ en("status", ["any", "open", "resolved"], "any"),
2292
+ list("colours")
2293
+ ],
2294
+ columns: [
2295
+ col("order", "#", "right"),
2296
+ col("colour", "Colour"),
2297
+ col("type", "Type"),
2298
+ col("title", "Title"),
2299
+ col("body", "Note"),
2300
+ col("author", "Author"),
2301
+ col("created", "Created"),
2302
+ col("modified", "Modified"),
2303
+ col("page", "Page"),
2304
+ col("scene", "Scene"),
2305
+ col("status", "Status"),
2306
+ col("replies", "Replies", "right")
2307
+ ]
2308
+ },
2309
+ synopsis: {
2310
+ featureId: "F-RPT-009",
2311
+ title: "Synopsis report",
2312
+ options: [bool("includeEmpty", true)],
2313
+ columns: [
2314
+ col("number", "Scene"),
2315
+ col("heading", "Heading"),
2316
+ col("synopsis", "Synopsis"),
2317
+ col("colour", "Colour")
2318
+ ]
2319
+ },
2320
+ revisions: {
2321
+ featureId: "F-RPT-010",
2322
+ title: "Revisions report",
2323
+ options: [list("sets", "Revision set ids; empty means every set")],
2324
+ columns: [
2325
+ col("set", "Revision"),
2326
+ col("colour", "Colour"),
2327
+ col("mark", "Mark"),
2328
+ col("date", "Date"),
2329
+ col("pages", "Pages"),
2330
+ col("scenes", "Scenes"),
2331
+ col("revisedElements", "Revised elements", "right")
2332
+ ]
2333
+ },
2334
+ tags: {
2335
+ featureId: "F-RPT-011",
2336
+ title: "Tags report",
2337
+ options: [
2338
+ en("mode", ["byScene", "byCategory"], "byScene"),
2339
+ list("categories", "Tag category ids; empty means all")
2340
+ ],
2341
+ columns: [
2342
+ col("scene", "Scene"),
2343
+ col("heading", "Heading"),
2344
+ col("page", "Page"),
2345
+ col("length", "Length"),
2346
+ col("scriptDay", "Script day"),
2347
+ col("category", "Category"),
2348
+ col("entities", "Entities")
2349
+ ]
2350
+ },
2351
+ statistics: {
2352
+ featureId: "F-RPT-012",
2353
+ title: "Statistics report",
2354
+ options: [
2355
+ { name: "topWords", type: "number", default: 20, min: 0, max: 200 },
2356
+ { name: "longestScenes", type: "number", default: 5, min: 0, max: 50 }
2357
+ ],
2358
+ columns: [col("metric", "Metric"), col("value", "Value", "right")]
2359
+ },
2360
+ inclusivity: {
2361
+ featureId: "F-RPT-013",
2362
+ title: "Inclusivity and gender analysis",
2363
+ options: [en("by", ["gender", "role"], "gender")],
2364
+ columns: [
2365
+ col("group", "Group"),
2366
+ col("characters", "Characters", "right"),
2367
+ col("dialogues", "Dialogues", "right"),
2368
+ col("words", "Words", "right"),
2369
+ col("scenesSpeaking", "Scenes speaking", "right"),
2370
+ col("wordsPct", "Words %", "right")
2371
+ ]
2372
+ },
2373
+ presentProgressive: {
2374
+ featureId: "F-RPT-014",
2375
+ title: "Present progressive check",
2376
+ options: [],
2377
+ columns: [
2378
+ col("page", "Page"),
2379
+ col("scene", "Scene"),
2380
+ col("match", "Match"),
2381
+ col("text", "Action line")
2382
+ ]
2383
+ },
2384
+ dayOutOfDays: {
2385
+ featureId: "F-RPT-015",
2386
+ title: "Day out of days",
2387
+ options: [],
2388
+ columns: [
2389
+ col("character", "Character"),
2390
+ col("workDays", "Work days", "right"),
2391
+ col("holds", "Holds", "right")
2392
+ ]
2393
+ },
2394
+ shotList: {
2395
+ featureId: "F-RPT-016",
2396
+ title: "Shot list",
2397
+ options: [],
2398
+ columns: [
2399
+ col("scene", "Scene"),
2400
+ col("shot", "Shot"),
2401
+ col("label", "Label"),
2402
+ col("description", "Description"),
2403
+ col("size", "Size"),
2404
+ col("angle", "Angle"),
2405
+ col("movement", "Movement"),
2406
+ col("lens", "Lens"),
2407
+ col("durationSec", "Duration (s)", "right")
2408
+ ]
2409
+ },
2410
+ structure: {
2411
+ featureId: "F-RPT-017",
2412
+ title: "Structure report",
2413
+ options: [],
2414
+ columns: [
2415
+ col("node", "Act / sequence / beat"),
2416
+ col("goal", "Goal pages"),
2417
+ col("actual", "Actual pages"),
2418
+ col("delta", "Delta", "right")
2419
+ ],
2420
+ unavailable: "Outline page goals are not modelled in writing_core yet (spec 01 §5.13 goals), so there is nothing to compare actual pages with"
2421
+ },
2422
+ characterArcs: {
2423
+ featureId: "F-RPT-018",
2424
+ title: "Character arcs",
2425
+ options: [
2426
+ list("characters", "Entity ids or names; empty means every character")
2427
+ ],
2428
+ columns: [
2429
+ col("character", "Character"),
2430
+ col("scene", "Scene"),
2431
+ col("heading", "Heading"),
2432
+ col("note", "Arc note")
2433
+ ]
2434
+ }
2435
+ };
2436
+ function reportKindInfos() {
2437
+ return REPORT_KINDS.map((kind) => {
2438
+ const d = REPORT_DEFS[kind];
2439
+ return {
2440
+ kind,
2441
+ featureId: d.featureId,
2442
+ title: d.title,
2443
+ available: !d.unavailable,
2444
+ ...d.unavailable ? { unavailableReason: d.unavailable } : {},
2445
+ options: d.options,
2446
+ columns: d.columns,
2447
+ formats: REPORT_FORMATS
2448
+ };
2449
+ });
2450
+ }
2451
+ const isReportKind = (v) => typeof v === "string" && REPORT_KINDS.includes(v);
2452
+ function parseReportOptions(kind, raw) {
2453
+ const defs = REPORT_DEFS[kind].options;
2454
+ const issues = [];
2455
+ if (raw === void 0 || raw === null) raw = {};
2456
+ if (typeof raw !== "object" || Array.isArray(raw))
2457
+ return {
2458
+ ok: false,
2459
+ issues: [{ path: "", message: "options must be an object" }]
2460
+ };
2461
+ const input = raw;
2462
+ const known = new Set(defs.map((d) => d.name));
2463
+ for (const k of Object.keys(input))
2464
+ if (!known.has(k)) issues.push({ path: k, message: "unknown option" });
2465
+ const out = {};
2466
+ for (const d of defs) {
2467
+ const v = input[d.name];
2468
+ if (v === void 0) {
2469
+ if (d.required) issues.push({ path: d.name, message: "required" });
2470
+ else if (d.default !== void 0)
2471
+ out[d.name] = Array.isArray(d.default) ? [...d.default] : d.default;
2472
+ continue;
2473
+ }
2474
+ switch (d.type) {
2475
+ case "boolean":
2476
+ if (typeof v !== "boolean")
2477
+ issues.push({ path: d.name, message: "must be a boolean" });
2478
+ else out[d.name] = v;
2479
+ break;
2480
+ case "number":
2481
+ if (typeof v !== "number" || !Number.isFinite(v) || d.min !== void 0 && v < d.min || d.max !== void 0 && v > d.max) {
2482
+ issues.push({
2483
+ path: d.name,
2484
+ message: `must be a number${d.min !== void 0 ? ` from ${d.min}` : ""}${d.max !== void 0 ? ` to ${d.max}` : ""}`
2485
+ });
2486
+ } else out[d.name] = v;
2487
+ break;
2488
+ case "string":
2489
+ if (typeof v !== "string" || d.required && v.length === 0 || v.length > 500)
2490
+ issues.push({
2491
+ path: d.name,
2492
+ message: "must be a non-empty string of at most 500 characters"
2493
+ });
2494
+ else out[d.name] = v;
2495
+ break;
2496
+ case "enum":
2497
+ if (typeof v !== "string" || !d.values.includes(v))
2498
+ issues.push({
2499
+ path: d.name,
2500
+ message: `must be one of ${d.values.join(", ")}`
2501
+ });
2502
+ else out[d.name] = v;
2503
+ break;
2504
+ case "stringList":
2505
+ if (!Array.isArray(v) || v.length > 500 || v.some((x) => typeof x !== "string"))
2506
+ issues.push({ path: d.name, message: "must be an array of strings" });
2507
+ else out[d.name] = v;
2508
+ break;
2509
+ }
2510
+ }
2511
+ return issues.length > 0 ? { ok: false, issues } : { ok: true, options: out };
2512
+ }
2513
+ const reportGetQuerySchema = z.object({
2514
+ source: sourceParamSchema.optional(),
2515
+ options: z.string().max(2e4).optional()
2516
+ });
2517
+ const reportCreateSchema = z.object({
2518
+ kind: z.enum(REPORT_KINDS),
2519
+ source: docSourceSchema.optional(),
2520
+ options: z.record(z.string(), z.unknown()).default({}),
2521
+ format: z.enum(REPORT_FORMATS).default("json")
2522
+ });
2523
+ const reportRenderInputSchema = z.object({
2524
+ kind: z.enum(REPORT_KINDS),
2525
+ source: z.string().default("live"),
2526
+ options: z.record(z.string(), z.unknown()).default({}),
2527
+ format: z.enum(REPORT_FORMATS).default("csv")
2528
+ });
2529
+ const cellText = (v) => v === null || v === void 0 ? "" : String(v);
2530
+ const csvField = (s) => /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
2531
+ function reportToCsv(result) {
2532
+ const blocks = result.tables.map((t) => {
2533
+ const lines = [];
2534
+ if (result.tables.length > 1 && t.title) lines.push(csvField(t.title));
2535
+ lines.push(t.columns.map((c) => csvField(c.label)).join(","));
2536
+ for (const r2 of t.rows)
2537
+ lines.push(
2538
+ t.columns.map((c) => csvField(cellText(r2[c.id] ?? null))).join(",")
2539
+ );
2540
+ if (t.footer)
2541
+ lines.push(
2542
+ t.columns.map((c) => csvField(cellText(t.footer[c.id] ?? null))).join(",")
2543
+ );
2544
+ return lines.join("\r\n");
2545
+ });
2546
+ return blocks.join("\r\n\r\n") + "\r\n";
2547
+ }
2548
+ const PACKET_VERSION = 1;
2549
+ const PACKET_INCLUDE_FLAGS = [
2550
+ "assets",
2551
+ "assetUrls",
2552
+ "script",
2553
+ "neighbors",
2554
+ "pages"
2555
+ ];
2556
+ const packetQuerySchema = z.object({
2557
+ snapshotId: z.string().min(1).optional(),
2558
+ include: z.preprocess(
2559
+ (v) => typeof v === "string" ? v.split(",").map((s) => s.trim()).filter(Boolean) : v,
2560
+ z.array(
2561
+ z.enum([
2562
+ "assets",
2563
+ "assetUrls=original",
2564
+ "script",
2565
+ "neighbors",
2566
+ "pages",
2567
+ "-assets",
2568
+ "-script"
2569
+ ])
2570
+ )
2571
+ ).optional()
2572
+ });
2573
+ const PACKET_MAX_BYTES = 2 * 1024 * 1024;
2574
+ const envelope = (kind, body) => z.object({
2575
+ packetVersion: z.literal(PACKET_VERSION),
2576
+ kind: z.literal(kind),
2577
+ documentId: z.string(),
2578
+ documentTitle: z.string(),
2579
+ snapshotId: z.string().nullable(),
2580
+ templateId: z.string(),
2581
+ language: z.string(),
2582
+ generatedAt: z.string(),
2583
+ /** `packetHash` of the packet without `generatedAt`, `hash` and signed-URL fields (spec 11 §4.2). */
2584
+ hash: z.string(),
2585
+ truncated: z.boolean().optional(),
2586
+ body
2587
+ });
2588
+ const assetSummary = z.object({
2589
+ assetId: z.string(),
2590
+ role: z.string(),
2591
+ kind: z.string(),
2592
+ title: z.string()
2593
+ }).passthrough();
2594
+ const pages = z.object({ start: z.string(), end: z.string(), eighths: z.number().int() }).nullable();
2595
+ const packetElementSchema = z.union([
2596
+ z.object({
2597
+ id: z.string(),
2598
+ type: z.literal("character"),
2599
+ text: z.string(),
2600
+ speakerEntityId: z.string().nullable(),
2601
+ extension: z.string().nullable()
2602
+ }),
2603
+ z.object({
2604
+ id: z.string(),
2605
+ type: z.literal("parenthetical"),
2606
+ text: z.string(),
2607
+ speakerEntityId: z.string().nullable()
2608
+ }),
2609
+ z.object({
2610
+ id: z.string(),
2611
+ type: z.literal("dialogue"),
2612
+ text: z.string(),
2613
+ speakerEntityId: z.string().nullable(),
2614
+ dual: z.boolean(),
2615
+ lineIndex: z.number().int()
2616
+ }),
2617
+ // every other role: sceneHeading, action, transition, shot, normal, ...
2618
+ z.object({
2619
+ id: z.string(),
2620
+ type: z.string().refine((t) => !["character", "parenthetical", "dialogue"].includes(t)),
2621
+ text: z.string()
2622
+ })
2623
+ ]);
2624
+ const shotSummary = z.object({
2625
+ id: z.string(),
2626
+ number: z.string().nullable(),
2627
+ label: z.string().nullable(),
2628
+ status: z.string(),
2629
+ camera: z.record(z.string(), z.unknown()),
2630
+ estimatedDurationMs: z.number().nullable(),
2631
+ description: z.string().nullable(),
2632
+ elementIds: z.array(z.string()),
2633
+ contentHash: z.string(),
2634
+ assets: z.array(assetSummary)
2635
+ });
2636
+ const sceneRef = z.object({
2637
+ id: z.string(),
2638
+ number: z.string().nullable(),
2639
+ heading: z.string(),
2640
+ synopsis: z.string()
2641
+ });
2642
+ const sceneHeader = z.object({
2643
+ id: z.string(),
2644
+ number: z.string().nullable(),
2645
+ ordinal: z.number().int(),
2646
+ omitted: z.boolean(),
2647
+ heading: z.object({
2648
+ raw: z.string(),
2649
+ intro: z.string().nullable(),
2650
+ location: z.string().nullable(),
2651
+ time: z.string().nullable()
2652
+ }),
2653
+ locationEntityId: z.string().nullable(),
2654
+ synopsis: z.string(),
2655
+ color: z.string().nullable(),
2656
+ pages,
2657
+ estimatedDurationMs: z.number(),
2658
+ folders: z.array(
2659
+ z.object({ id: z.string(), kind: z.string(), title: z.string() })
2660
+ ),
2661
+ contentHash: z.string(),
2662
+ scriptDay: z.string().nullable()
2663
+ });
2664
+ const locationBlock = z.object({
2665
+ entityId: z.string(),
2666
+ name: z.string(),
2667
+ fields: z.record(z.string(), z.unknown()),
2668
+ description: z.string(),
2669
+ assets: z.array(assetSummary)
2670
+ }).nullable();
2671
+ const scenePacketBodySchema = z.object({
2672
+ scene: sceneHeader,
2673
+ script: z.array(packetElementSchema),
2674
+ cast: z.array(
2675
+ z.object({
2676
+ entityId: z.string(),
2677
+ name: z.string(),
2678
+ speaking: z.boolean(),
2679
+ lineCount: z.number().int(),
2680
+ wordCount: z.number().int(),
2681
+ firstAppearance: z.boolean(),
2682
+ summary: z.string(),
2683
+ fields: z.record(z.string(), z.unknown()),
2684
+ traits: z.record(z.string(), z.union([z.string(), z.number()])),
2685
+ assets: z.array(assetSummary)
2686
+ })
2687
+ ),
2688
+ location: locationBlock,
2689
+ elements: z.array(
2690
+ z.object({
2691
+ entityId: z.string(),
2692
+ kind: z.string(),
2693
+ name: z.string(),
2694
+ description: z.string(),
2695
+ tagCount: z.number().int(),
2696
+ assets: z.array(assetSummary)
2697
+ })
2698
+ ),
2699
+ shots: z.array(shotSummary),
2700
+ notes: z.array(
2701
+ z.object({
2702
+ id: z.string(),
2703
+ type: z.string(),
2704
+ text: z.string(),
2705
+ anchorElementId: z.string().nullable()
2706
+ })
2707
+ ),
2708
+ sceneAssets: z.array(assetSummary),
2709
+ neighbors: z.object({ previous: sceneRef.nullable(), next: sceneRef.nullable() }).optional()
2710
+ });
2711
+ const scenePacketSchema = envelope("scene", scenePacketBodySchema);
2712
+ const characterPacketBodySchema = z.object({
2713
+ character: z.object({
2714
+ id: z.string(),
2715
+ name: z.string(),
2716
+ aliases: z.array(z.string()),
2717
+ description: z.string(),
2718
+ fields: z.record(z.string(), z.unknown()),
2719
+ traits: z.record(z.string(), z.union([z.string(), z.number()])),
2720
+ attributes: z.record(z.string(), z.unknown()),
2721
+ contentHash: z.string()
2722
+ }),
2723
+ ages: z.array(
2724
+ z.object({
2725
+ id: z.string(),
2726
+ label: z.string(),
2727
+ ageValue: z.number().nullable(),
2728
+ description: z.string(),
2729
+ skeleton: z.array(
2730
+ z.object({
2731
+ section: z.string().nullable(),
2732
+ label: z.string(),
2733
+ answer: z.string()
2734
+ })
2735
+ ),
2736
+ appearances: z.array(
2737
+ z.object({
2738
+ id: z.string(),
2739
+ label: z.string(),
2740
+ description: z.string(),
2741
+ assetIds: z.array(z.string())
2742
+ })
2743
+ ),
2744
+ sceneCount: z.number().int()
2745
+ })
2746
+ ),
2747
+ skeleton: z.array(
2748
+ z.object({
2749
+ section: z.string().nullable(),
2750
+ label: z.string(),
2751
+ answer: z.string()
2752
+ })
2753
+ ),
2754
+ stats: z.object({
2755
+ sceneCount: z.number().int(),
2756
+ speakingSceneCount: z.number().int(),
2757
+ lineCount: z.number().int(),
2758
+ wordCount: z.number().int(),
2759
+ firstSceneId: z.string().nullable(),
2760
+ lastSceneId: z.string().nullable()
2761
+ }),
2762
+ scenes: z.array(
2763
+ z.object({
2764
+ id: z.string(),
2765
+ number: z.string().nullable(),
2766
+ heading: z.string(),
2767
+ speaking: z.boolean(),
2768
+ lineCount: z.number().int(),
2769
+ ageId: z.string().nullable(),
2770
+ appearanceId: z.string().nullable()
2771
+ })
2772
+ ),
2773
+ sampleDialogue: z.array(
2774
+ z.object({ elementId: z.string(), sceneId: z.string(), text: z.string() })
2775
+ ),
2776
+ relationships: z.array(
2777
+ z.object({
2778
+ entityId: z.string(),
2779
+ name: z.string(),
2780
+ sharedSceneCount: z.number().int()
2781
+ })
2782
+ ),
2783
+ props: z.array(z.object({ entityId: z.string(), name: z.string() })),
2784
+ assets: z.array(assetSummary)
2785
+ });
2786
+ const characterPacketSchema = envelope(
2787
+ "character",
2788
+ characterPacketBodySchema
2789
+ );
2790
+ const locationPacketBodySchema = z.object({
2791
+ location: z.object({
2792
+ id: z.string(),
2793
+ name: z.string(),
2794
+ aliases: z.array(z.string()),
2795
+ description: z.string(),
2796
+ fields: z.record(z.string(), z.unknown()),
2797
+ parent: z.object({ id: z.string(), name: z.string() }).nullable(),
2798
+ children: z.array(z.object({ id: z.string(), name: z.string() })),
2799
+ contentHash: z.string()
2800
+ }),
2801
+ versions: z.array(
2802
+ z.object({
2803
+ id: z.string(),
2804
+ label: z.string(),
2805
+ description: z.string(),
2806
+ setting: z.string().nullable(),
2807
+ sceneCount: z.number().int()
2808
+ })
2809
+ ),
2810
+ scenes: z.array(
2811
+ z.object({
2812
+ id: z.string(),
2813
+ number: z.string().nullable(),
2814
+ heading: z.string(),
2815
+ time: z.string().nullable(),
2816
+ pages,
2817
+ cast: z.array(z.string()),
2818
+ versionId: z.string().nullable()
2819
+ })
2820
+ ),
2821
+ totals: z.object({
2822
+ sceneCount: z.number().int(),
2823
+ eighths: z.number().int(),
2824
+ timesOfDay: z.record(z.string(), z.number().int())
2825
+ }),
2826
+ setElements: z.array(
2827
+ z.object({ entityId: z.string(), kind: z.string(), name: z.string() })
2828
+ ),
2829
+ assets: z.array(assetSummary)
2830
+ });
2831
+ const locationPacketSchema = envelope(
2832
+ "location",
2833
+ locationPacketBodySchema
2834
+ );
2835
+ const shotPacketBodySchema = z.object({
2836
+ shot: shotSummary.extend({
2837
+ sceneId: z.string(),
2838
+ range: z.object({
2839
+ startElementId: z.string(),
2840
+ endElementId: z.string(),
2841
+ text: z.string()
2842
+ }).nullable()
2843
+ }),
2844
+ scene: z.object({
2845
+ id: z.string(),
2846
+ number: z.string().nullable(),
2847
+ heading: z.object({
2848
+ raw: z.string(),
2849
+ intro: z.string().nullable(),
2850
+ location: z.string().nullable(),
2851
+ time: z.string().nullable()
2852
+ }),
2853
+ locationEntityId: z.string().nullable(),
2854
+ contentHash: z.string()
2855
+ }),
2856
+ script: z.array(packetElementSchema),
2857
+ subjects: z.array(
2858
+ z.object({
2859
+ entityId: z.string(),
2860
+ kind: z.string(),
2861
+ name: z.string(),
2862
+ fields: z.record(z.string(), z.unknown()),
2863
+ assets: z.array(assetSummary)
2864
+ })
2865
+ ),
2866
+ location: locationBlock,
2867
+ previousShot: shotSummary.nullable(),
2868
+ nextShot: shotSummary.nullable()
2869
+ });
2870
+ const shotPacketSchema = envelope("shot", shotPacketBodySchema);
2871
+ const ASSET_KINDS = [
2872
+ "image",
2873
+ "video",
2874
+ "audio",
2875
+ "model3d",
2876
+ "document",
2877
+ "archive",
2878
+ "data"
2879
+ ];
2880
+ const assetKindSchema = z.enum(ASSET_KINDS);
2881
+ const ASSET_ROLES = [
2882
+ // launch
2883
+ "reference",
2884
+ "headshot",
2885
+ "photo",
2886
+ "logo",
2887
+ "beat_image",
2888
+ "inline_image",
2889
+ "panel_reference",
2890
+ "location_scout",
2891
+ "mood_board",
2892
+ "attachment",
2893
+ // future production (valid now, unused by launch UI)
2894
+ "turnaround",
2895
+ "expression_sheet",
2896
+ "costume_design",
2897
+ "set_concept",
2898
+ "set_plan",
2899
+ "storyboard_frame",
2900
+ "animatic",
2901
+ "previz",
2902
+ "rendered_video",
2903
+ "rendered_shot",
2904
+ "dialogue_audio",
2905
+ "voice_sample",
2906
+ "voice_profile",
2907
+ "music_cue",
2908
+ "sound_effect",
2909
+ "lip_sync",
2910
+ "captions",
2911
+ "model3d",
2912
+ "model3d_rig",
2913
+ "texture",
2914
+ "lora",
2915
+ "timeline"
2916
+ ];
2917
+ const assetRoleSchema = z.enum(ASSET_ROLES);
2918
+ const ASSET_ROLE_GROUPS = {
2919
+ reference: "reference",
2920
+ headshot: "reference",
2921
+ photo: "reference",
2922
+ logo: "other",
2923
+ beat_image: "other",
2924
+ inline_image: "other",
2925
+ panel_reference: "storyboard",
2926
+ location_scout: "reference",
2927
+ mood_board: "reference",
2928
+ attachment: "other",
2929
+ turnaround: "design",
2930
+ expression_sheet: "design",
2931
+ costume_design: "design",
2932
+ set_concept: "design",
2933
+ set_plan: "design",
2934
+ storyboard_frame: "storyboard",
2935
+ animatic: "video",
2936
+ previz: "video",
2937
+ rendered_video: "video",
2938
+ rendered_shot: "video",
2939
+ dialogue_audio: "audio",
2940
+ voice_sample: "audio",
2941
+ voice_profile: "audio",
2942
+ music_cue: "audio",
2943
+ sound_effect: "audio",
2944
+ lip_sync: "video",
2945
+ captions: "other",
2946
+ model3d: "3d",
2947
+ model3d_rig: "3d",
2948
+ texture: "3d",
2949
+ lora: "other",
2950
+ timeline: "other"
2951
+ };
2952
+ const ASSET_TARGET_KINDS = [
2953
+ "document",
2954
+ "scene",
2955
+ "element",
2956
+ "shot",
2957
+ "entity",
2958
+ "beat",
2959
+ "title_page",
2960
+ "snapshot"
2961
+ ];
2962
+ const assetTargetKindSchema = z.enum(ASSET_TARGET_KINDS);
2963
+ const ASSET_ROLE_TARGETS = {
2964
+ headshot: ["entity"],
2965
+ turnaround: ["entity"],
2966
+ expression_sheet: ["entity"],
2967
+ costume_design: ["entity"],
2968
+ voice_sample: ["entity"],
2969
+ voice_profile: ["entity"],
2970
+ set_concept: ["entity", "scene"],
2971
+ set_plan: ["entity", "scene"],
2972
+ location_scout: ["entity", "scene"],
2973
+ beat_image: ["beat"],
2974
+ inline_image: ["element"],
2975
+ panel_reference: ["element", "scene", "shot"],
2976
+ logo: ["title_page", "document"],
2977
+ storyboard_frame: ["shot", "scene"],
2978
+ animatic: ["shot", "scene", "document"],
2979
+ previz: ["shot", "scene", "document"],
2980
+ rendered_video: ["shot", "scene", "document"],
2981
+ rendered_shot: ["shot"],
2982
+ dialogue_audio: ["element", "shot", "scene"],
2983
+ music_cue: ["scene", "shot", "document"],
2984
+ sound_effect: ["scene", "shot", "element"],
2985
+ lip_sync: ["shot", "element"],
2986
+ captions: ["scene", "shot", "document"],
2987
+ timeline: ["document", "scene"]
2988
+ };
2989
+ const ASSET_ROLE_ENTITY_KINDS = {
2990
+ headshot: ["character"],
2991
+ turnaround: ["character"],
2992
+ expression_sheet: ["character"],
2993
+ costume_design: ["character"],
2994
+ voice_sample: ["character"],
2995
+ voice_profile: ["character"],
2996
+ set_concept: ["location"],
2997
+ set_plan: ["location"],
2998
+ location_scout: ["location"]
2999
+ };
3000
+ const ASSET_ORIGINS = [
3001
+ "upload",
3002
+ "import",
3003
+ "generated",
3004
+ "external_tool"
3005
+ ];
3006
+ const ASSET_VERSION_STATUSES = [
3007
+ "uploading",
3008
+ "processing",
3009
+ "ready",
3010
+ "failed"
3011
+ ];
3012
+ const STALENESS_VALUES = [
3013
+ "fresh",
3014
+ "stale",
3015
+ "target_deleted",
3016
+ "unknown"
3017
+ ];
3018
+ const ASSET_PART_SIZE_BYTES = 8 * 1024 * 1024;
3019
+ const ASSET_UPLOAD_CONCURRENCY = 3;
3020
+ const ASSET_UPLOAD_TTL_S = 3600;
3021
+ const ASSET_URL_TTL_S = 300;
3022
+ const ASSET_MAX_BYTES = {
3023
+ image: 100 * 1024 ** 2,
3024
+ video: 10 * 1024 ** 3,
3025
+ audio: 2 * 1024 ** 3,
3026
+ model3d: 5 * 1024 ** 3,
3027
+ document: 200 * 1024 ** 2,
3028
+ archive: 10 * 1024 ** 3,
3029
+ data: 50 * 1024 ** 2
3030
+ };
3031
+ const ASSET_EXTENSIONS = {
3032
+ ".png": { kind: "image", mimes: ["image/png"] },
3033
+ ".jpg": { kind: "image", mimes: ["image/jpeg"] },
3034
+ ".jpeg": { kind: "image", mimes: ["image/jpeg"] },
3035
+ ".webp": { kind: "image", mimes: ["image/webp"] },
3036
+ ".avif": { kind: "image", mimes: ["image/avif"] },
3037
+ ".gif": { kind: "image", mimes: ["image/gif"] },
3038
+ ".heic": { kind: "image", mimes: ["image/heic"] },
3039
+ ".heif": { kind: "image", mimes: ["image/heif"] },
3040
+ ".svg": { kind: "image", mimes: ["image/svg+xml"] },
3041
+ ".tif": { kind: "image", mimes: ["image/tiff"] },
3042
+ ".tiff": { kind: "image", mimes: ["image/tiff"] },
3043
+ ".exr": { kind: "image", mimes: ["image/x-exr"] },
3044
+ ".psd": { kind: "image", mimes: ["image/vnd.adobe.photoshop"] },
3045
+ ".mp4": { kind: "video", mimes: ["video/mp4"] },
3046
+ ".mov": { kind: "video", mimes: ["video/quicktime"] },
3047
+ ".webm": { kind: "video", mimes: ["video/webm"] },
3048
+ ".mkv": { kind: "video", mimes: ["video/x-matroska"] },
3049
+ ".wav": { kind: "audio", mimes: ["audio/wav", "audio/x-wav", "audio/wave"] },
3050
+ ".aif": { kind: "audio", mimes: ["audio/x-aiff", "audio/aiff"] },
3051
+ ".aiff": { kind: "audio", mimes: ["audio/x-aiff", "audio/aiff"] },
3052
+ ".flac": { kind: "audio", mimes: ["audio/flac"] },
3053
+ ".mp3": { kind: "audio", mimes: ["audio/mpeg"] },
3054
+ ".aac": { kind: "audio", mimes: ["audio/aac"] },
3055
+ ".m4a": { kind: "audio", mimes: ["audio/mp4", "audio/x-m4a"] },
3056
+ ".ogg": { kind: "audio", mimes: ["audio/ogg"] },
3057
+ ".opus": { kind: "audio", mimes: ["audio/ogg", "audio/opus"] },
3058
+ ".glb": { kind: "model3d", mimes: ["model/gltf-binary"] },
3059
+ ".gltf": { kind: "model3d", mimes: ["model/gltf+json"] },
3060
+ ".usdz": { kind: "model3d", mimes: ["model/vnd.usdz+zip"] },
3061
+ ".blend": {
3062
+ kind: "model3d",
3063
+ mimes: ["application/x-blender", "application/octet-stream"]
3064
+ },
3065
+ ".fbx": {
3066
+ kind: "model3d",
3067
+ mimes: ["model/vnd.fbx", "application/octet-stream"]
3068
+ },
3069
+ ".obj": { kind: "model3d", mimes: ["model/obj", "text/plain"] },
3070
+ ".stl": {
3071
+ kind: "model3d",
3072
+ mimes: ["model/stl", "application/sla", "application/octet-stream"]
3073
+ },
3074
+ ".usda": { kind: "model3d", mimes: ["model/vnd.usda", "text/plain"] },
3075
+ ".usdc": {
3076
+ kind: "model3d",
3077
+ mimes: ["model/vnd.usda", "application/octet-stream"]
3078
+ },
3079
+ ".pdf": { kind: "document", mimes: ["application/pdf"] },
3080
+ ".txt": { kind: "document", mimes: ["text/plain"] },
3081
+ ".md": { kind: "document", mimes: ["text/markdown"] },
3082
+ ".docx": {
3083
+ kind: "document",
3084
+ mimes: [
3085
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
3086
+ ]
3087
+ },
3088
+ ".zip": {
3089
+ kind: "archive",
3090
+ mimes: ["application/zip", "application/x-zip-compressed"]
3091
+ },
3092
+ ".json": { kind: "data", mimes: ["application/json"] },
3093
+ ".csv": { kind: "data", mimes: ["text/csv"] },
3094
+ ".srt": { kind: "data", mimes: ["application/x-subrip", "text/plain"] },
3095
+ ".vtt": { kind: "data", mimes: ["text/vtt"] },
3096
+ ".otio": {
3097
+ kind: "data",
3098
+ mimes: ["application/otio+json", "application/json"]
3099
+ },
3100
+ ".fcpxml": {
3101
+ kind: "data",
3102
+ mimes: ["application/x-fcpxml", "application/xml", "text/xml"]
3103
+ },
3104
+ ".edl": { kind: "data", mimes: ["application/edl", "text/plain"] }
3105
+ };
3106
+ const assetExtension = (filename) => {
3107
+ const m = /(\.[A-Za-z0-9]{1,10})$/.exec(filename);
3108
+ return m ? m[1].toLowerCase() : "";
3109
+ };
3110
+ function checkAssetFile(filename, mimeType, sizeBytes, kind) {
3111
+ const spec = ASSET_EXTENSIONS[assetExtension(filename)];
3112
+ if (!spec) return "type";
3113
+ if (spec.kind !== kind) return "kind";
3114
+ if (!spec.mimes.includes(mimeType.toLowerCase().split(";")[0].trim()))
3115
+ return "mime";
3116
+ if (sizeBytes > ASSET_MAX_BYTES[kind]) return "size";
3117
+ return null;
3118
+ }
3119
+ const assetRightsSchema = z.object({
3120
+ ownership: z.enum(["owned", "licensed", "public_domain", "generated", "unknown"]).default("unknown"),
3121
+ license: z.string().max(500).optional(),
3122
+ attribution: z.string().max(500).optional(),
3123
+ sourceUrl: z.string().max(2e3).optional(),
3124
+ restrictions: z.string().max(1e3).optional(),
3125
+ expiresAt: z.string().optional(),
3126
+ containsLikenessOf: z.array(z.string().max(200)).max(50).optional(),
3127
+ consentAssetId: z.string().optional(),
3128
+ aiTrainingAllowed: z.boolean().default(false)
3129
+ });
3130
+ const assetSourceRefSchema = z.object({
3131
+ documentId: z.string(),
3132
+ snapshotId: z.string().optional(),
3133
+ targetKind: z.enum([
3134
+ "document",
3135
+ "scene",
3136
+ "element",
3137
+ "shot",
3138
+ "entity",
3139
+ "beat",
3140
+ "title_page"
3141
+ ]),
3142
+ targetId: z.string(),
3143
+ sourceHash: z.string()
3144
+ });
3145
+ const assetProvenanceInputSchema = z.discriminatedUnion("origin", [
3146
+ z.object({
3147
+ origin: z.literal("external_tool"),
3148
+ tool: z.string().min(1).max(100),
3149
+ toolVersion: z.string().max(100).optional(),
3150
+ description: z.string().max(2e3).optional(),
3151
+ sourceRefs: z.array(assetSourceRefSchema).max(100).default([]),
3152
+ createdVia: z.enum(["mcp", "api", "app"]).default("api")
3153
+ }),
3154
+ z.object({
3155
+ origin: z.literal("generated"),
3156
+ jobId: z.string().optional(),
3157
+ adapter: z.string().min(1).max(100),
3158
+ provider: z.string().min(1).max(100),
3159
+ model: z.string().max(200).optional(),
3160
+ prompt: z.string().max(2e4).optional(),
3161
+ negativePrompt: z.string().max(2e4).optional(),
3162
+ parameters: z.record(z.string(), z.unknown()).default({}),
3163
+ inputs: z.array(
3164
+ z.object({
3165
+ assetVersionId: z.string().optional(),
3166
+ packetHash: z.string().optional(),
3167
+ role: z.string()
3168
+ })
3169
+ ).max(100).default([]),
3170
+ sourceRefs: z.array(assetSourceRefSchema).max(100).default([]),
3171
+ costCredits: z.number().optional(),
3172
+ providerJobId: z.string().optional(),
3173
+ createdVia: z.enum(["mcp", "api", "app"]).default("api")
3174
+ })
3175
+ ]);
3176
+ const sha256HexSchema = z.string().regex(/^[0-9a-fA-F]{64}$/, "sha256Hex must be 64 hex characters");
3177
+ const assetUploadRequestSchema = z.object({
3178
+ /** Client-minted `asset_...` id for offline staging; accepted when unused (`ID_CONFLICT` otherwise). */
3179
+ assetId: z.string().regex(/^asset_[0-9A-Za-z]{10,40}$/).optional(),
3180
+ filename: z.string().min(1).max(500),
3181
+ mimeType: z.string().min(1).max(200),
3182
+ sizeBytes: z.number().int().min(1),
3183
+ sha256Hex: sha256HexSchema.optional(),
3184
+ kind: assetKindSchema,
3185
+ title: z.string().trim().min(1).max(200).optional(),
3186
+ /** A new version of an existing asset: the version it derives from. */
3187
+ parentVersionId: z.string().min(1).optional(),
3188
+ provenance: assetProvenanceInputSchema.optional(),
3189
+ rights: assetRightsSchema.partial().optional()
3190
+ });
3191
+ const assetPartsRequestSchema = z.object({
3192
+ partNumbers: z.array(z.number().int().min(1).max(1e4)).min(1).max(100)
3193
+ });
3194
+ const assetCompleteSchema = z.object({
3195
+ parts: z.array(
3196
+ z.object({
3197
+ partNumber: z.number().int().min(1),
3198
+ etag: z.string().min(1).max(200)
3199
+ })
3200
+ ).max(1e4),
3201
+ sha256Hex: sha256HexSchema.optional()
3202
+ });
3203
+ const linkedToSchema = z.string().regex(
3204
+ /^(entity|scene|shot|element):[A-Za-z0-9_-]+$/,
3205
+ "linkedTo is <entity|scene|shot|element>:<id>"
3206
+ );
3207
+ const assetListQuerySchema = cursorQuerySchema.extend({
3208
+ workspaceId: z.string().min(1).optional(),
3209
+ documentId: z.string().min(1).optional(),
3210
+ linkedTo: linkedToSchema.optional(),
3211
+ role: assetRoleSchema.optional(),
3212
+ kind: assetKindSchema.optional(),
3213
+ q: z.string().trim().min(1).max(200).optional(),
3214
+ origin: z.enum(ASSET_ORIGINS).optional()
3215
+ });
3216
+ const assetUpdateSchema = z.object({
3217
+ title: z.string().trim().min(1).max(200).optional(),
3218
+ description: z.string().max(5e3).optional(),
3219
+ rights: assetRightsSchema.partial().optional()
3220
+ }).refine((v) => Object.keys(v).length > 0, "Nothing to update");
3221
+ const ASSET_URL_VARIANTS = [
3222
+ "original",
3223
+ "thumb_256",
3224
+ "thumb_512",
3225
+ "preview_1600",
3226
+ "poster",
3227
+ "proxy_720p",
3228
+ "proxy_1080p",
3229
+ "preview",
3230
+ "waveform",
3231
+ "sprite"
3232
+ ];
3233
+ const assetUrlQuerySchema = z.object({
3234
+ variant: z.string().regex(/^[a-z0-9_]{1,40}$/).default("original")
3235
+ });
3236
+ const assetLinkCreateSchema = z.object({
3237
+ assetId: z.string().min(1),
3238
+ pinnedVersionId: z.string().min(1).optional(),
3239
+ /** Null/omitted = a workspace-level link: the target must then be `{kind: 'document', id: <the workspace id>}`. */
3240
+ documentId: z.string().min(1).nullable().optional(),
3241
+ target: z.object({
3242
+ kind: assetTargetKindSchema,
3243
+ id: z.string().min(1).max(200)
3244
+ }),
3245
+ role: assetRoleSchema,
3246
+ sortOrder: z.number().int().min(0).max(1e6).optional(),
3247
+ /** Version-prefixed content hash of the target when the asset was derived from it (spec 11 §4.2). */
3248
+ sourceHash: z.string().min(1).max(200).optional(),
3249
+ sourceSnapshotId: z.string().min(1).optional(),
3250
+ note: z.string().max(2e3).optional()
3251
+ });
3252
+ const assetLinkUpdateSchema = z.object({
3253
+ role: assetRoleSchema.optional(),
3254
+ sortOrder: z.number().int().min(0).max(1e6).optional(),
3255
+ /** `null` unpins (follow the current version). */
3256
+ pinnedVersionId: z.string().min(1).nullable().optional(),
3257
+ note: z.string().max(2e3).optional()
3258
+ }).refine((v) => Object.keys(v).length > 0, "Nothing to update");
3259
+ const boolQuery$1 = z.enum(["true", "false"]).transform((v) => v === "true");
3260
+ const documentAssetLinksQuerySchema = z.object({
3261
+ targetKind: assetTargetKindSchema.optional(),
3262
+ targetId: z.string().min(1).optional(),
3263
+ role: assetRoleSchema.optional(),
3264
+ withStaleness: boolQuery$1.optional()
3265
+ });
3266
+ const STALENESS_TARGET_KINDS = [
3267
+ "scene",
3268
+ "element",
3269
+ "shot",
3270
+ "entity"
3271
+ ];
3272
+ const documentStalenessQuerySchema = z.object({
3273
+ targetKind: z.enum(STALENESS_TARGET_KINDS).optional()
3274
+ });
3275
+ const assetDeriveInputSchema = z.object({
3276
+ assetVersionId: z.string().min(1)
3277
+ });
3278
+ const NOTIFICATION_KINDS = [
3279
+ "mention",
3280
+ "note_reply",
3281
+ "note_on_my_document",
3282
+ "snapshot_comment",
3283
+ "chat_mention",
3284
+ "invitation",
3285
+ "share_granted",
3286
+ "snapshot_opened_by_collaborator",
3287
+ "job_finished",
3288
+ "job_failed",
3289
+ "ai_suggestions_ready",
3290
+ "ai_refunded",
3291
+ "export_ready",
3292
+ "watermark_batch_sent",
3293
+ "writing_reminder",
3294
+ "credits_low",
3295
+ "purchase_completed",
3296
+ "account_deletion_scheduled"
3297
+ ];
3298
+ const NOTIFICATION_EMAIL_MODES = ["immediate", "digest", "off"];
3299
+ const DEFAULT_IMMEDIATE_EMAIL = /* @__PURE__ */ new Set([
3300
+ "mention",
3301
+ "note_reply",
3302
+ "chat_mention",
3303
+ "invitation",
3304
+ "share_granted",
3305
+ "job_failed",
3306
+ "export_ready",
3307
+ "watermark_batch_sent",
3308
+ "credits_low",
3309
+ "purchase_completed",
3310
+ "account_deletion_scheduled"
3311
+ ]);
3312
+ const DEFAULT_DIGEST_EMAIL = /* @__PURE__ */ new Set([
3313
+ "snapshot_comment",
3314
+ "note_on_my_document",
3315
+ "ai_suggestions_ready",
3316
+ "ai_refunded",
3317
+ "snapshot_opened_by_collaborator"
3318
+ ]);
3319
+ const DEFAULT_QUIET = /* @__PURE__ */ new Set([
3320
+ "job_finished",
3321
+ "writing_reminder"
3322
+ ]);
3323
+ function defaultNotificationKindPrefs(kind) {
3324
+ const email = DEFAULT_IMMEDIATE_EMAIL.has(kind) ? "immediate" : DEFAULT_DIGEST_EMAIL.has(kind) ? "digest" : "off";
3325
+ return { inApp: true, push: !DEFAULT_QUIET.has(kind), email };
3326
+ }
3327
+ function defaultNotificationPrefs() {
3328
+ return {
3329
+ kinds: Object.fromEntries(
3330
+ NOTIFICATION_KINDS.map((k) => [k, defaultNotificationKindPrefs(k)])
3331
+ ),
3332
+ writingReminderTime: null
3333
+ };
3334
+ }
3335
+ const notificationKindPrefsPatchSchema = z.object({
3336
+ inApp: z.boolean().optional(),
3337
+ push: z.boolean().optional(),
3338
+ email: z.enum(NOTIFICATION_EMAIL_MODES).optional()
3339
+ });
3340
+ const HHMM_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
3341
+ const notificationPrefsSchema = z.object({
3342
+ kinds: z.record(z.string(), notificationKindPrefsPatchSchema).refine(
3343
+ (obj) => Object.keys(obj).every(
3344
+ (k) => NOTIFICATION_KINDS.includes(k)
3345
+ ),
3346
+ {
3347
+ message: "unknown notification kind"
3348
+ }
3349
+ ).optional(),
3350
+ writingReminderTime: z.string().regex(HHMM_RE, "writingReminderTime must be HH:MM (24h, local)").nullable().optional()
3351
+ });
3352
+ function mergeNotificationPrefs(current, update) {
3353
+ const kinds = { ...current.kinds };
3354
+ for (const [k, patch] of Object.entries(update.kinds ?? {})) {
3355
+ const kind = k;
3356
+ kinds[kind] = {
3357
+ ...kinds[kind] ?? defaultNotificationKindPrefs(kind),
3358
+ ...patch
3359
+ };
3360
+ }
3361
+ return {
3362
+ kinds,
3363
+ writingReminderTime: update.writingReminderTime === void 0 ? current.writingReminderTime : update.writingReminderTime
3364
+ };
3365
+ }
3366
+ const boolQuery = z.enum(["true", "false"]).transform((v) => v === "true");
3367
+ const notificationsQuerySchema = cursorQuerySchema.extend({
3368
+ unread: boolQuery.optional()
3369
+ });
3370
+ const notificationsReadSchema = z.object({
3371
+ ids: z.array(z.string().min(1)).max(200).optional(),
3372
+ all: z.literal(true).optional(),
3373
+ before: z.iso.datetime({ offset: true }).optional()
3374
+ }).refine(
3375
+ (v) => v.ids !== void 0 || v.all !== void 0 || v.before !== void 0,
3376
+ {
3377
+ message: "one of ids, all or before is required"
3378
+ }
3379
+ );
3380
+ const CHAT_BODY_MAX = 4e3;
3381
+ const CHAT_EDIT_WINDOW_MINUTES = 15;
3382
+ const mentionRefSchema = z.object({
3383
+ userId: z.string().min(1),
3384
+ offset: z.number().int().min(0).optional(),
3385
+ length: z.number().int().min(1).optional()
3386
+ });
3387
+ const chatMessageCreateSchema = z.object({
3388
+ clientMessageId: z.string().min(8).max(64).regex(/^msg_[0-9A-Za-z]+$/, "clientMessageId must be a msg_ prefixed id"),
3389
+ body: z.string().trim().min(1).max(CHAT_BODY_MAX),
3390
+ mentions: z.array(mentionRefSchema).max(50).optional()
3391
+ });
3392
+ const chatMessageUpdateSchema = z.object({
3393
+ body: z.string().trim().min(1).max(CHAT_BODY_MAX)
3394
+ });
3395
+ const chatQuerySchema = cursorQuerySchema.extend({
3396
+ before: z.iso.datetime({ offset: true }).optional()
3397
+ });
3398
+ const ACTIVITY_KINDS = [
3399
+ "document.created",
3400
+ "document.renamed",
3401
+ "document.moved",
3402
+ "document.trashed",
3403
+ "document.restored",
3404
+ "session.edited",
3405
+ "snapshot.created",
3406
+ "snapshot.opened",
3407
+ "snapshot.forked",
3408
+ "version.restored",
3409
+ "template.applied",
3410
+ "import.over",
3411
+ "revision.setCreated",
3412
+ "locks.changed",
3413
+ "changes.decided",
3414
+ "ai.jobCompleted",
3415
+ "ai.suggestionsDecided",
3416
+ "share.granted",
3417
+ "share.linkCreated",
3418
+ "member.joined",
3419
+ "member.left",
3420
+ "mcp.commandsApplied"
3421
+ ];
3422
+ const ACTOR_KINDS = ["user", "mcp", "api", "ai", "system"];
3423
+ const csvList = (v) => typeof v === "string" ? v.split(",").map((s) => s.trim()).filter(Boolean) : v;
3424
+ const activityQuerySchema = cursorQuerySchema.extend({
3425
+ kinds: z.preprocess(csvList, z.array(z.string())).optional()
3426
+ });
3427
+ const workspaceActivityQuerySchema = cursorQuerySchema.extend({
3428
+ projectId: z.string().min(1).optional()
3429
+ });
3430
+ const DEVICE_PLATFORMS = [
3431
+ "web",
3432
+ "ios",
3433
+ "ipados",
3434
+ "android",
3435
+ "macos",
3436
+ "windows"
3437
+ ];
3438
+ const PUSH_PLATFORMS = ["apns", "fcm", "wns", "webpush"];
3439
+ const deviceCreateSchema = z.object({
3440
+ id: z.string().min(8).max(64).regex(/^dev_[0-9A-Za-z]+$/, "id must be a dev_ prefixed id"),
3441
+ installId: z.string().min(1).max(128),
3442
+ displayName: z.string().trim().min(1).max(80),
3443
+ platform: z.enum(DEVICE_PLATFORMS),
3444
+ appVersion: z.string().min(1).max(40)
3445
+ });
3446
+ const deviceUpdateSchema = z.object({
3447
+ displayName: z.string().trim().min(1).max(80)
3448
+ });
3449
+ const pushTokenSetSchema = z.object({
3450
+ platform: z.enum(PUSH_PLATFORMS),
3451
+ token: z.string().min(1).max(4096),
3452
+ environment: z.enum(["production", "sandbox"]).optional()
3453
+ });
3454
+ const CONSUMABLE_SOURCES = ["web", "apple", "google", "free"];
3455
+ const purchaseHandoffRequestSchema = z.object({
3456
+ productId: z.string().optional(),
3457
+ returnTo: z.string().min(1).max(2e3)
3458
+ });
3459
+ const purchaseHandoffRedeemSchema = z.object({
3460
+ token: z.string().min(1)
3461
+ });
3462
+ const telemetryEventSchema = z.object({
3463
+ name: z.string().min(1).max(120),
3464
+ ts: z.string(),
3465
+ /** Allow-listed, flat properties only: no script text, no free-form nesting. */
3466
+ properties: z.record(
3467
+ z.string(),
3468
+ z.union([z.string().max(500), z.number(), z.boolean(), z.null()])
3469
+ ).optional()
3470
+ });
3471
+ const clientErrorReportSchema = z.object({
3472
+ message: z.string().min(1).max(2e3),
3473
+ stack: z.string().max(8e3).optional(),
3474
+ context: z.record(
3475
+ z.string(),
3476
+ z.union([z.string().max(500), z.number(), z.boolean(), z.null()])
3477
+ ).optional()
3478
+ });
3479
+ const telemetryRequestSchema = z.object({
3480
+ events: z.array(telemetryEventSchema).max(100),
3481
+ errors: z.array(clientErrorReportSchema).max(20).optional()
3482
+ });
3483
+ const adminJobListQuerySchema = cursorQuerySchema.extend({
3484
+ status: z.enum(JOB_STATUSES).optional(),
3485
+ kind: z.string().optional()
3486
+ });
3487
+ const adminJobKindPatchSchema = z.object({
3488
+ enabled: z.boolean().optional(),
3489
+ maxAttempts: z.number().int().min(1).max(10).optional(),
3490
+ maxRetries: z.number().int().min(0).max(10).optional(),
3491
+ maxConcurrentPerUser: z.number().int().min(1).nullable().optional(),
3492
+ maxConcurrentGlobal: z.number().int().min(1).nullable().optional(),
3493
+ timeoutMs: z.number().int().min(1e3).optional()
3494
+ });
3495
+ const adminJobRefundSchema = z.object({
3496
+ credits: z.number().int().positive().optional()
3497
+ });
3498
+ const COMMENTER_WRITABLE_KEYS = ["notes"];
3499
+ const isCommenterMark = (name) => name === "hl" || name.startsWith("n:");
3500
+ function markPermissionsFor(role) {
3501
+ const edit = role === "owner" || role === "admin" || role === "writer";
3502
+ const comment = edit || role === "commenter";
3503
+ return { edit, comment, highlight: comment };
3504
+ }
3505
+ const canSendUpdates = (role) => role !== "viewer";
3506
+ const SYNC_PROTOCOL_VERSION = 1;
3507
+ const SYNC_SUBPROTOCOL = "fadewright-sync.v1";
3508
+ const SYNC_PATH = "/api/v1/sync";
3509
+ const SYNC_HEARTBEAT_MS = 25e3;
3510
+ const SYNC_MAX_FRAME_BYTES = 1048576;
3511
+ const SYNC_AUTH_TIMEOUT_MS = 5e3;
3512
+ const SYNC_MESSAGE_TYPES = {
3513
+ auth: 0,
3514
+ authOk: 1,
3515
+ authError: 2,
3516
+ reauth: 3,
3517
+ ping: 4,
3518
+ pong: 4,
3519
+ subscribe: 10,
3520
+ subscribed: 11,
3521
+ subscribeError: 12,
3522
+ unsubscribe: 13,
3523
+ syncStep1: 20,
3524
+ syncStep2: 21,
3525
+ update: 22,
3526
+ ack: 23,
3527
+ reject: 24,
3528
+ awareness: 30,
3529
+ epochChanged: 40,
3530
+ roleChanged: 41,
3531
+ documentDeleted: 42,
3532
+ normalized: 43,
3533
+ serverShutdown: 50
3534
+ };
3535
+ const SYNC_CLOSE_CODES = {
3536
+ NORMAL: 1e3,
3537
+ GOING_AWAY: 1001,
3538
+ MALFORMED_FRAME: 4400,
3539
+ AUTH_TIMEOUT_OR_INVALID: 4401,
3540
+ ACCOUNT_DISABLED_OR_DEVICE_REVOKED: 4403,
3541
+ PROTOCOL_UNSUPPORTED: 4406,
3542
+ HEARTBEAT_TIMEOUT: 4408,
3543
+ /** Document epoch changed (snapshot open / version restore): resubscribe at the new epoch. */
3544
+ EPOCH_CHANGED: 4409,
3545
+ FRAME_TOO_LARGE: 4413,
3546
+ RATE_LIMITED: 4429
3547
+ };
3548
+ const SYNC_AUTH_SCHEMES = ["firebase", "apikey", "link"];
3549
+ const SYNC_AUTH_ERROR_CODES = [
3550
+ "UNAUTHENTICATED",
3551
+ "ACCOUNT_DISABLED",
3552
+ "DEVICE_REVOKED",
3553
+ "LINK_EXPIRED",
3554
+ "CLIENT_TOO_OLD",
3555
+ "SERVER_TOO_OLD"
3556
+ ];
3557
+ const SYNC_SUBSCRIBE_ERROR_CODES = [
3558
+ "NOT_FOUND",
3559
+ "FORBIDDEN",
3560
+ "DOCUMENT_DELETED",
3561
+ "EPOCH_MISMATCH",
3562
+ "DOCUMENT_LOCKED",
3563
+ "CLIENT_TOO_OLD"
3564
+ ];
3565
+ const SYNC_REJECT_CODES = [
3566
+ "FORBIDDEN",
3567
+ "EPOCH_MISMATCH",
3568
+ "UPDATE_INVALID",
3569
+ "PAYLOAD_TOO_LARGE",
3570
+ "SCHEMA_VIOLATION"
3571
+ ];
3572
+ const SYNC_SUBSCRIBE_MODES = ["edit", "view"];
3573
+ const SYNC_EPOCH_REASONS = [
3574
+ "snapshot-open",
3575
+ "import-over",
3576
+ "version-restore",
3577
+ "schema-migration"
3578
+ ];
3579
+ const roleOrNone = z.enum([
3580
+ "owner",
3581
+ "admin",
3582
+ "writer",
3583
+ "commenter",
3584
+ "viewer",
3585
+ "none"
3586
+ ]);
3587
+ const bytes = z.instanceof(Uint8Array);
3588
+ const uint = z.number().int().min(0);
3589
+ const authPayloadSchema = z.object({
3590
+ scheme: z.enum(SYNC_AUTH_SCHEMES),
3591
+ token: z.string(),
3592
+ deviceId: z.string(),
3593
+ installId: z.string(),
3594
+ clientVersion: z.string(),
3595
+ schemaVersion: uint,
3596
+ /** Unlock-session token (`POST /documents/:did/unlock-session`) for opening locked documents. */
3597
+ unlockToken: z.string().optional()
3598
+ });
3599
+ const authOkPayloadSchema = z.object({
3600
+ userId: z.string(),
3601
+ sessionId: z.string(),
3602
+ serverSchemaVersion: uint,
3603
+ heartbeatMs: uint,
3604
+ maxFrameBytes: uint
3605
+ });
3606
+ const authErrorPayloadSchema = z.object({
3607
+ code: z.enum(SYNC_AUTH_ERROR_CODES),
3608
+ message: z.string()
3609
+ });
3610
+ const reauthPayloadSchema = z.object({ token: z.string() });
3611
+ const pingPayloadSchema = z.object({ t: z.number() });
3612
+ const subscribePayloadSchema = z.object({
3613
+ channelId: uint.min(1),
3614
+ documentId: z.string(),
3615
+ epoch: uint,
3616
+ /** base64 state vector */
3617
+ stateVector: z.string().optional(),
3618
+ mode: z.enum(SYNC_SUBSCRIBE_MODES),
3619
+ snapshotId: z.string().optional()
3620
+ });
3621
+ const subscribedPayloadSchema = z.object({
3622
+ documentId: z.string(),
3623
+ role: roleOrNone,
3624
+ epoch: uint,
3625
+ markPermissions: z.unknown(),
3626
+ schemaVersion: uint
3627
+ });
3628
+ const subscribeErrorPayloadSchema = z.object({
3629
+ code: z.enum(SYNC_SUBSCRIBE_ERROR_CODES),
3630
+ detail: z.unknown().optional()
3631
+ });
3632
+ const unsubscribePayloadSchema = z.object({ channelId: uint.min(1) });
3633
+ const ackPayloadSchema = z.object({
3634
+ clientSeq: uint,
3635
+ serverSeq: uint
3636
+ });
3637
+ const rejectPayloadSchema = z.object({
3638
+ clientSeq: uint,
3639
+ code: z.enum(SYNC_REJECT_CODES),
3640
+ detail: z.unknown().optional()
3641
+ });
3642
+ const epochChangedPayloadSchema = z.object({
3643
+ newEpoch: uint,
3644
+ reason: z.enum(SYNC_EPOCH_REASONS),
3645
+ byUserId: z.string(),
3646
+ snapshotId: z.string().optional()
3647
+ });
3648
+ const roleChangedPayloadSchema = z.object({
3649
+ role: roleOrNone,
3650
+ markPermissions: z.unknown()
3651
+ });
3652
+ const documentDeletedPayloadSchema = z.object({
3653
+ byUserId: z.string(),
3654
+ recoverableUntil: z.string()
3655
+ });
3656
+ const normalizedPayloadSchema = z.object({
3657
+ serverSeq: uint,
3658
+ contentHashes: z.record(z.string(), z.string())
3659
+ });
3660
+ const serverShutdownPayloadSchema = z.object({
3661
+ reconnectAfterMs: uint
3662
+ });
3663
+ const ctl = z.literal(0);
3664
+ const chan = uint.min(1);
3665
+ const syncFrameSchema = z.discriminatedUnion("type", [
3666
+ z.object({
3667
+ channelId: ctl,
3668
+ type: z.literal("auth"),
3669
+ payload: authPayloadSchema
3670
+ }),
3671
+ z.object({
3672
+ channelId: ctl,
3673
+ type: z.literal("authOk"),
3674
+ payload: authOkPayloadSchema
3675
+ }),
3676
+ z.object({
3677
+ channelId: ctl,
3678
+ type: z.literal("authError"),
3679
+ payload: authErrorPayloadSchema
3680
+ }),
3681
+ z.object({
3682
+ channelId: ctl,
3683
+ type: z.literal("reauth"),
3684
+ payload: reauthPayloadSchema
3685
+ }),
3686
+ z.object({
3687
+ channelId: ctl,
3688
+ type: z.literal("ping"),
3689
+ payload: pingPayloadSchema
3690
+ }),
3691
+ z.object({
3692
+ channelId: ctl,
3693
+ type: z.literal("pong"),
3694
+ payload: pingPayloadSchema
3695
+ }),
3696
+ z.object({
3697
+ channelId: ctl,
3698
+ type: z.literal("subscribe"),
3699
+ payload: subscribePayloadSchema
3700
+ }),
3701
+ z.object({
3702
+ channelId: chan,
3703
+ type: z.literal("subscribed"),
3704
+ payload: subscribedPayloadSchema
3705
+ }),
3706
+ z.object({
3707
+ channelId: chan,
3708
+ type: z.literal("subscribeError"),
3709
+ payload: subscribeErrorPayloadSchema
3710
+ }),
3711
+ z.object({
3712
+ channelId: ctl,
3713
+ type: z.literal("unsubscribe"),
3714
+ payload: unsubscribePayloadSchema
3715
+ }),
3716
+ z.object({ channelId: chan, type: z.literal("syncStep1"), payload: bytes }),
3717
+ z.object({ channelId: chan, type: z.literal("syncStep2"), payload: bytes }),
3718
+ z.object({
3719
+ channelId: chan,
3720
+ type: z.literal("update"),
3721
+ payload: z.object({ epoch: uint, clientSeq: uint, update: bytes })
3722
+ }),
3723
+ z.object({
3724
+ channelId: chan,
3725
+ type: z.literal("ack"),
3726
+ payload: ackPayloadSchema
3727
+ }),
3728
+ z.object({
3729
+ channelId: chan,
3730
+ type: z.literal("reject"),
3731
+ payload: rejectPayloadSchema
3732
+ }),
3733
+ z.object({ channelId: chan, type: z.literal("awareness"), payload: bytes }),
3734
+ z.object({
3735
+ channelId: chan,
3736
+ type: z.literal("epochChanged"),
3737
+ payload: epochChangedPayloadSchema
3738
+ }),
3739
+ z.object({
3740
+ channelId: chan,
3741
+ type: z.literal("roleChanged"),
3742
+ payload: roleChangedPayloadSchema
3743
+ }),
3744
+ z.object({
3745
+ channelId: chan,
3746
+ type: z.literal("documentDeleted"),
3747
+ payload: documentDeletedPayloadSchema
3748
+ }),
3749
+ z.object({
3750
+ channelId: chan,
3751
+ type: z.literal("normalized"),
3752
+ payload: normalizedPayloadSchema
3753
+ }),
3754
+ z.object({
3755
+ channelId: ctl,
3756
+ type: z.literal("serverShutdown"),
3757
+ payload: serverShutdownPayloadSchema
3758
+ })
3759
+ ]);
3760
+ const BIT8 = 128;
3761
+ const BITS7 = 127;
3762
+ const floor = Math.floor;
3763
+ const min = (a, b) => a < b ? a : b;
3764
+ const max = (a, b) => a > b ? a : b;
3765
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
3766
+ const _encodeUtf8Polyfill = (str) => {
3767
+ const encodedString = unescape(encodeURIComponent(str));
3768
+ const len = encodedString.length;
3769
+ const buf = new Uint8Array(len);
3770
+ for (let i = 0; i < len; i++) {
3771
+ buf[i] = /** @type {number} */
3772
+ encodedString.codePointAt(i);
3773
+ }
3774
+ return buf;
3775
+ };
3776
+ const utf8TextEncoder = (
3777
+ /** @type {TextEncoder} */
3778
+ typeof TextEncoder !== "undefined" ? new TextEncoder() : null
3779
+ );
3780
+ const _encodeUtf8Native = (str) => utf8TextEncoder.encode(str);
3781
+ const encodeUtf8 = utf8TextEncoder ? _encodeUtf8Native : _encodeUtf8Polyfill;
3782
+ const _decodeUtf8Polyfill = (buf) => {
3783
+ let remainingLen = buf.length;
3784
+ let encodedString = "";
3785
+ let bufPos = 0;
3786
+ while (remainingLen > 0) {
3787
+ const nextLen = remainingLen < 1e4 ? remainingLen : 1e4;
3788
+ const bytes2 = buf.subarray(bufPos, bufPos + nextLen);
3789
+ bufPos += nextLen;
3790
+ encodedString += String.fromCodePoint.apply(
3791
+ null,
3792
+ /** @type {any} */
3793
+ bytes2
3794
+ );
3795
+ remainingLen -= nextLen;
3796
+ }
3797
+ return decodeURIComponent(escape(encodedString));
3798
+ };
3799
+ let utf8TextDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
3800
+ if (utf8TextDecoder && utf8TextDecoder.decode(new Uint8Array()).length === 1) {
3801
+ utf8TextDecoder = null;
3802
+ }
3803
+ const _decodeUtf8Native = (buf) => (
3804
+ /** @type {TextDecoder} */
3805
+ utf8TextDecoder.decode(buf)
3806
+ );
3807
+ const decodeUtf8 = utf8TextDecoder ? _decodeUtf8Native : _decodeUtf8Polyfill;
3808
+ const create = (s) => new Error(s);
3809
+ class Encoder {
3810
+ constructor() {
3811
+ this.cpos = 0;
3812
+ this.cbuf = new Uint8Array(100);
3813
+ this.bufs = [];
3814
+ }
3815
+ }
3816
+ const createEncoder = () => new Encoder();
3817
+ const length = (encoder) => {
3818
+ let len = encoder.cpos;
3819
+ for (let i = 0; i < encoder.bufs.length; i++) {
3820
+ len += encoder.bufs[i].length;
3821
+ }
3822
+ return len;
3823
+ };
3824
+ const toUint8Array = (encoder) => {
3825
+ const uint8arr = new Uint8Array(length(encoder));
3826
+ let curPos = 0;
3827
+ for (let i = 0; i < encoder.bufs.length; i++) {
3828
+ const d = encoder.bufs[i];
3829
+ uint8arr.set(d, curPos);
3830
+ curPos += d.length;
3831
+ }
3832
+ uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
3833
+ return uint8arr;
3834
+ };
3835
+ const write = (encoder, num) => {
3836
+ const bufferLen = encoder.cbuf.length;
3837
+ if (encoder.cpos === bufferLen) {
3838
+ encoder.bufs.push(encoder.cbuf);
3839
+ encoder.cbuf = new Uint8Array(bufferLen * 2);
3840
+ encoder.cpos = 0;
3841
+ }
3842
+ encoder.cbuf[encoder.cpos++] = num;
3843
+ };
3844
+ const writeVarUint = (encoder, num) => {
3845
+ while (num > BITS7) {
3846
+ write(encoder, BIT8 | BITS7 & num);
3847
+ num = floor(num / 128);
3848
+ }
3849
+ write(encoder, BITS7 & num);
3850
+ };
3851
+ const _strBuffer = new Uint8Array(3e4);
3852
+ const _maxStrBSize = _strBuffer.length / 3;
3853
+ const _writeVarStringNative = (encoder, str) => {
3854
+ if (str.length < _maxStrBSize) {
3855
+ const written = utf8TextEncoder.encodeInto(str, _strBuffer).written || 0;
3856
+ writeVarUint(encoder, written);
3857
+ for (let i = 0; i < written; i++) {
3858
+ write(encoder, _strBuffer[i]);
3859
+ }
3860
+ } else {
3861
+ writeVarUint8Array(encoder, encodeUtf8(str));
3862
+ }
3863
+ };
3864
+ const _writeVarStringPolyfill = (encoder, str) => {
3865
+ const encodedString = unescape(encodeURIComponent(str));
3866
+ const len = encodedString.length;
3867
+ writeVarUint(encoder, len);
3868
+ for (let i = 0; i < len; i++) {
3869
+ write(
3870
+ encoder,
3871
+ /** @type {number} */
3872
+ encodedString.codePointAt(i)
3873
+ );
3874
+ }
3875
+ };
3876
+ utf8TextEncoder && /** @type {any} */
3877
+ utf8TextEncoder.encodeInto ? _writeVarStringNative : _writeVarStringPolyfill;
3878
+ const writeUint8Array = (encoder, uint8Array) => {
3879
+ const bufferLen = encoder.cbuf.length;
3880
+ const cpos = encoder.cpos;
3881
+ const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
3882
+ const rightCopyLen = uint8Array.length - leftCopyLen;
3883
+ encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
3884
+ encoder.cpos += leftCopyLen;
3885
+ if (rightCopyLen > 0) {
3886
+ encoder.bufs.push(encoder.cbuf);
3887
+ encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
3888
+ encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
3889
+ encoder.cpos = rightCopyLen;
3890
+ }
3891
+ };
3892
+ const writeVarUint8Array = (encoder, uint8Array) => {
3893
+ writeVarUint(encoder, uint8Array.byteLength);
3894
+ writeUint8Array(encoder, uint8Array);
3895
+ };
3896
+ const errorUnexpectedEndOfArray = create("Unexpected end of array");
3897
+ const errorIntegerOutOfRange = create("Integer out of Range");
3898
+ class Decoder {
3899
+ /**
3900
+ * @param {Uint8Array<Buf>} uint8Array Binary data to decode
3901
+ */
3902
+ constructor(uint8Array) {
3903
+ this.arr = uint8Array;
3904
+ this.pos = 0;
3905
+ }
3906
+ }
3907
+ const createDecoder = (uint8Array) => new Decoder(uint8Array);
3908
+ const hasContent = (decoder) => decoder.pos !== decoder.arr.length;
3909
+ const readUint8Array = (decoder, len) => {
3910
+ const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
3911
+ decoder.pos += len;
3912
+ return view;
3913
+ };
3914
+ const readVarUint8Array = (decoder) => readUint8Array(decoder, readVarUint(decoder));
3915
+ const readTailAsUint8Array = (decoder) => readUint8Array(decoder, decoder.arr.length - decoder.pos);
3916
+ const readVarUint = (decoder) => {
3917
+ let num = 0;
3918
+ let mult = 1;
3919
+ const len = decoder.arr.length;
3920
+ while (decoder.pos < len) {
3921
+ const r2 = decoder.arr[decoder.pos++];
3922
+ num = num + (r2 & BITS7) * mult;
3923
+ mult *= 128;
3924
+ if (r2 < BIT8) {
3925
+ return num;
3926
+ }
3927
+ if (num > MAX_SAFE_INTEGER) {
3928
+ throw errorIntegerOutOfRange;
3929
+ }
3930
+ }
3931
+ throw errorUnexpectedEndOfArray;
3932
+ };
3933
+ class SyncCodecError extends Error {
3934
+ constructor(message) {
3935
+ super(message);
3936
+ this.name = "SyncCodecError";
3937
+ }
3938
+ }
3939
+ const BINARY_TYPES = /* @__PURE__ */ new Set(["syncStep1", "syncStep2", "awareness"]);
3940
+ const NAME_BY_CODE = /* @__PURE__ */ new Map();
3941
+ for (const [name, code] of Object.entries(SYNC_MESSAGE_TYPES)) {
3942
+ if (!NAME_BY_CODE.has(code))
3943
+ NAME_BY_CODE.set(code, name);
3944
+ }
3945
+ function encodeFrame(frame) {
3946
+ const enc = createEncoder();
3947
+ writeVarUint(enc, frame.channelId);
3948
+ writeVarUint(enc, SYNC_MESSAGE_TYPES[frame.type]);
3949
+ if (BINARY_TYPES.has(frame.type)) {
3950
+ writeVarUint8Array(enc, frame.payload);
3951
+ } else if (frame.type === "update") {
3952
+ writeVarUint(enc, frame.payload.epoch);
3953
+ writeVarUint(enc, frame.payload.clientSeq);
3954
+ writeVarUint8Array(enc, frame.payload.update);
3955
+ } else {
3956
+ writeUint8Array(
3957
+ enc,
3958
+ encodeUtf8(JSON.stringify(frame.payload))
3959
+ );
3960
+ }
3961
+ return toUint8Array(enc);
3962
+ }
3963
+ function decodeFrame(bytes2) {
3964
+ let candidate;
3965
+ try {
3966
+ const dec = createDecoder(bytes2);
3967
+ const channelId = readVarUint(dec);
3968
+ const code = readVarUint(dec);
3969
+ const type = NAME_BY_CODE.get(code);
3970
+ if (!type) throw new SyncCodecError(`unknown message type ${code}`);
3971
+ let payload;
3972
+ if (BINARY_TYPES.has(type)) {
3973
+ payload = readVarUint8Array(dec);
3974
+ if (hasContent(dec)) throw new SyncCodecError("trailing bytes");
3975
+ } else if (type === "update") {
3976
+ const epoch = readVarUint(dec);
3977
+ const clientSeq = readVarUint(dec);
3978
+ const update = readVarUint8Array(dec);
3979
+ if (hasContent(dec)) throw new SyncCodecError("trailing bytes");
3980
+ payload = { epoch, clientSeq, update };
3981
+ } else {
3982
+ const rest = readTailAsUint8Array(dec);
3983
+ payload = JSON.parse(decodeUtf8(rest));
3984
+ }
3985
+ candidate = { channelId, type, payload };
3986
+ } catch (e) {
3987
+ if (e instanceof SyncCodecError) throw e;
3988
+ throw new SyncCodecError(
3989
+ `malformed frame: ${e instanceof Error ? e.message : String(e)}`
3990
+ );
3991
+ }
3992
+ const parsed = syncFrameSchema.safeParse(candidate);
3993
+ if (!parsed.success)
3994
+ throw new SyncCodecError(`invalid frame: ${parsed.error.message}`);
3995
+ return parsed.data;
3996
+ }
3997
+ export {
3998
+ ACCOUNT_DELETE_CONFIRM,
3999
+ ACCOUNT_EXPORT_FILENAME,
4000
+ ACTIVITY_KINDS,
4001
+ ACTOR_KINDS,
4002
+ AI_CONSENT_VERSION,
4003
+ AI_ENDPOINTS,
4004
+ AI_ENDPOINT_OUTPUT_SCHEMAS,
4005
+ AI_JOB_STATUSES,
4006
+ AI_LIMITS,
4007
+ AI_TASKS,
4008
+ API_BASE_PATH,
4009
+ API_ERROR_CODES,
4010
+ API_KEY_FORMAT_RE,
4011
+ API_KEY_MAX_ACTIVE,
4012
+ API_KEY_SCOPES,
4013
+ API_ROUTES,
4014
+ ASSET_EXTENSIONS,
4015
+ ASSET_KINDS,
4016
+ ASSET_MAX_BYTES,
4017
+ ASSET_ORIGINS,
4018
+ ASSET_PART_SIZE_BYTES,
4019
+ ASSET_ROLES,
4020
+ ASSET_ROLE_ENTITY_KINDS,
4021
+ ASSET_ROLE_GROUPS,
4022
+ ASSET_ROLE_TARGETS,
4023
+ ASSET_TARGET_KINDS,
4024
+ ASSET_UPLOAD_CONCURRENCY,
4025
+ ASSET_UPLOAD_TTL_S,
4026
+ ASSET_URL_TTL_S,
4027
+ ASSET_URL_VARIANTS,
4028
+ ASSET_VERSION_STATUSES,
4029
+ AUDIT_CSV_COLUMNS,
4030
+ AUDIT_MAX_RANGE_DAYS,
4031
+ AUTOCORRECT_MAX_PAIRS,
4032
+ BATCH_WATERMARK_MAX_RECIPIENTS,
4033
+ CHAT_BODY_MAX,
4034
+ CHAT_EDIT_WINDOW_MINUTES,
4035
+ CLIENT_AUTO_REASONS,
4036
+ CLIENT_HEADER,
4037
+ COMBINED_EXPORT_FORMATS,
4038
+ COMBINED_EXPORT_MAX_DOCUMENTS,
4039
+ COMMAND_BATCH_MAX,
4040
+ COMMENTER_WRITABLE_KEYS,
4041
+ COMPARE_GRANULARITIES,
4042
+ COMPARE_MAX_PAGES,
4043
+ CONSUMABLE_SOURCES,
4044
+ CONVERSION_REPORT_OUTPUT,
4045
+ COVERAGE_VERDICTS,
4046
+ DEFAULT_PROJECT_ROLES,
4047
+ DEVICE_CLASSES,
4048
+ DEVICE_PLATFORMS,
4049
+ DICTIONARY_MAX_WORDS,
4050
+ DOCUMENT_KINDS2 as DOCUMENT_KINDS,
4051
+ DOCUMENT_LABELS_MAX,
4052
+ DOCUMENT_LABEL_MAX_CHARS,
4053
+ DOCUMENT_VIEW_STATE_MAX_BYTES,
4054
+ ELEMENT_BATCH_MAX,
4055
+ ERROR_STATUS,
4056
+ EXPORT_CODE_PREFIX,
4057
+ EXPORT_FORMAT_IDS,
4058
+ FOLDER_MAX_DEPTH,
4059
+ FOUNTAIN_MAX_BYTES,
4060
+ FWTEMPLATE_FORMAT,
4061
+ FWTEMPLATE_FORMAT_VERSION,
4062
+ IDEMPOTENCY_HEADER,
4063
+ IDEMPOTENT_REPLAY_HEADER,
4064
+ ID_PREFIXES,
4065
+ IMPORT_FORMAT_IDS,
4066
+ IMPORT_MAX_BYTES_DEFAULT,
4067
+ INVITATION_STATUSES,
4068
+ INVITATION_TARGETS,
4069
+ INVITATION_TTL_DAYS,
4070
+ JOB_EXPORT_FORMATS,
4071
+ JOB_IMPORT_FORMATS,
4072
+ JOB_KINDS,
4073
+ JOB_LANES,
4074
+ JOB_STATUSES,
4075
+ JOB_WAIT_MAX_S,
4076
+ JOB_WEBHOOK_SIGNATURE_HEADER,
4077
+ LINK_SESSION_PREFIX,
4078
+ LINK_SESSION_TTL_S,
4079
+ LOCATOR_KINDS,
4080
+ MACRO_TRIGGER_KINDS,
4081
+ MCP_CLIENT_NAME,
4082
+ NOTIFICATION_EMAIL_MODES,
4083
+ NOTIFICATION_KINDS,
4084
+ PACKET_INCLUDE_FLAGS,
4085
+ PACKET_MAX_BYTES,
4086
+ PACKET_VERSION,
4087
+ PERMISSIONS,
4088
+ PERMISSION_MIN_ROLE,
4089
+ PROJECT_BIN_ITEM_MAX_BYTES,
4090
+ PROJECT_KINDS,
4091
+ PUSH_PLATFORMS,
4092
+ REAUTH_MAX_AGE_S,
4093
+ RECENT_SEARCHES_MAX,
4094
+ REPORT_DEFS,
4095
+ REPORT_FORMATS,
4096
+ REPORT_KINDS,
4097
+ REPORT_SYNC_MAX_PAGES,
4098
+ RESOLVE_BATCH_MAX,
4099
+ REVIEW_CATEGORIES,
4100
+ ROLES,
4101
+ ROLE_PERMISSIONS,
4102
+ SCENE_BATCH_MAX,
4103
+ SEARCH_HIT_TYPES,
4104
+ SEVERITIES,
4105
+ SHARE_LINK_ACCESS,
4106
+ SHARE_LINK_GENERAL_ACCESS,
4107
+ SHARE_LINK_TARGETS,
4108
+ SHARE_LINK_TOKEN_PREFIX,
4109
+ SHARE_ROLES,
4110
+ SNAPSHOT_AUTO_REASONS,
4111
+ SNAPSHOT_KINDS,
4112
+ SNAPSHOT_NOTES_MAX,
4113
+ SORT_OPTIONS,
4114
+ STALENESS_TARGET_KINDS,
4115
+ STALENESS_VALUES,
4116
+ SUGGESTION_SET_STATUSES,
4117
+ SUGGESTION_STATUSES,
4118
+ SYNC_AUTH_ERROR_CODES,
4119
+ SYNC_AUTH_SCHEMES,
4120
+ SYNC_AUTH_TIMEOUT_MS,
4121
+ SYNC_CLOSE_CODES,
4122
+ SYNC_EPOCH_REASONS,
4123
+ SYNC_HEARTBEAT_MS,
4124
+ SYNC_MAX_FRAME_BYTES,
4125
+ SYNC_MESSAGE_TYPES,
4126
+ SYNC_PATH,
4127
+ SYNC_PROTOCOL_VERSION,
4128
+ SYNC_REJECT_CODES,
4129
+ SYNC_SUBPROTOCOL,
4130
+ SYNC_SUBSCRIBE_ERROR_CODES,
4131
+ SYNC_SUBSCRIBE_MODES,
4132
+ SyncCodecError,
4133
+ TEAM_WORKSPACE_LIMIT,
4134
+ TEMPLATE_FILE_FORMATS,
4135
+ TERMINAL_JOB_STATUSES,
4136
+ TRACKED_CHANGE_MODES,
4137
+ TRASH_ITEM_TYPES,
4138
+ TRASH_RETENTION_DAYS,
4139
+ UI_LANGUAGES,
4140
+ UNLOCK_SESSION_TTL_S,
4141
+ UPLOAD_PURPOSES,
4142
+ USER_MACRO_MAX,
4143
+ USER_PREFERENCES_KEYS,
4144
+ USER_PREFERENCES_MAX_BYTES,
4145
+ USER_PREFERENCES_VERSION,
4146
+ VERSION_REASONS,
4147
+ WORKSPACE_CONTACTS_BATCH_MAX,
4148
+ WORKSPACE_DEFAULTS_MAX_BYTES,
4149
+ WORKSPACE_KINDS,
4150
+ WRITING_GOALS_MAX,
4151
+ WRITING_GOAL_KINDS,
4152
+ WRITING_STATS_GRANULARITIES,
4153
+ accountDeleteSchema,
4154
+ accountExportInputSchema,
4155
+ ackPayloadSchema,
4156
+ activityQuerySchema,
4157
+ adminJobKindPatchSchema,
4158
+ adminJobListQuerySchema,
4159
+ adminJobRefundSchema,
4160
+ aiConsentAcceptSchema,
4161
+ aiEndpointInputSchema,
4162
+ aiEstimateRequestSchema,
4163
+ aiJobCreateSchema,
4164
+ aiJobOptionsSchema,
4165
+ aiKindForTask,
4166
+ aiScopeSchema,
4167
+ aiTaskSchema,
4168
+ anchorRefSchema,
4169
+ apiKeyCreateSchema,
4170
+ apiKeyScopeSchema,
4171
+ apiKeyUpdateSchema,
4172
+ applyTemplateJobInputSchema,
4173
+ assetCompleteSchema,
4174
+ assetDeriveInputSchema,
4175
+ assetExtension,
4176
+ assetKindSchema,
4177
+ assetLinkCreateSchema,
4178
+ assetLinkUpdateSchema,
4179
+ assetListQuerySchema,
4180
+ assetPartsRequestSchema,
4181
+ assetProvenanceInputSchema,
4182
+ assetRightsSchema,
4183
+ assetRoleSchema,
4184
+ assetSourceRefSchema,
4185
+ assetTargetKindSchema,
4186
+ assetUpdateSchema,
4187
+ assetUploadRequestSchema,
4188
+ assetUrlQuerySchema,
4189
+ authErrorPayloadSchema,
4190
+ authOkPayloadSchema,
4191
+ authPayloadSchema,
4192
+ batchWatermarkSchema,
4193
+ canSendUpdates,
4194
+ cellText,
4195
+ changesQuerySchema,
4196
+ characterPacketBodySchema,
4197
+ characterPacketSchema,
4198
+ chatMessageCreateSchema,
4199
+ chatMessageUpdateSchema,
4200
+ chatQuerySchema,
4201
+ checkAssetFile,
4202
+ chunkSummaryResponseSchema,
4203
+ clientErrorReportSchema,
4204
+ commandBatchRequestSchema,
4205
+ compareRequestSchema,
4206
+ coverageResponseSchema,
4207
+ cursorQuerySchema,
4208
+ decodeFrame,
4209
+ defaultNotificationKindPrefs,
4210
+ defaultNotificationPrefs,
4211
+ deviceCreateSchema,
4212
+ deviceUpdateSchema,
4213
+ dictionaryUpdateSchema,
4214
+ docSourceSchema,
4215
+ documentApplyTemplateSchema,
4216
+ documentAssetLinksQuerySchema,
4217
+ documentCreateSchema,
4218
+ documentDeletedPayloadSchema,
4219
+ documentDuplicateSchema,
4220
+ documentExportSchema,
4221
+ documentImportSchema,
4222
+ documentKindSchema,
4223
+ documentLabelsSchema,
4224
+ documentListQuerySchema,
4225
+ documentMoveSchema,
4226
+ documentSearchQuerySchema,
4227
+ documentStalenessQuerySchema,
4228
+ documentUpdateSchema,
4229
+ documentViewStatePutSchema,
4230
+ documentViewStateSchema,
4231
+ elementHistoryQuerySchema,
4232
+ elementsBatchRequestSchema,
4233
+ encodeFrame,
4234
+ entityDialogueQuerySchema,
4235
+ entityListQuerySchema,
4236
+ entityUsageQuerySchema,
4237
+ epochChangedPayloadSchema,
4238
+ errorResponse,
4239
+ exportCombinedSchema,
4240
+ exportCreateSchema,
4241
+ exportJobInputSchema,
4242
+ exportOptionsSchema,
4243
+ fountainQuerySchema,
4244
+ grantUpdateSchema,
4245
+ hasPermission,
4246
+ idKind,
4247
+ idempotencyKeySchema,
4248
+ importCreateSchema,
4249
+ importJobInputSchema,
4250
+ importOptionsSchema,
4251
+ importOverJobInputSchema,
4252
+ importOverSchema,
4253
+ invitationAcceptSchema,
4254
+ invitationCreateSchema,
4255
+ isAiKind,
4256
+ isCommenterMark,
4257
+ isId,
4258
+ isReportKind,
4259
+ isTerminalJobStatus,
4260
+ jobCreateSchema,
4261
+ jobGetQuerySchema,
4262
+ jobKindSchema,
4263
+ jobListQuerySchema,
4264
+ jobSourceSchema,
4265
+ locationPacketBodySchema,
4266
+ locationPacketSchema,
4267
+ markPermissionsFor,
4268
+ maxRole,
4269
+ meUpdateSchema,
4270
+ memberUpdateSchema,
4271
+ mentionRefSchema,
4272
+ mergeNotificationPrefs,
4273
+ minRole,
4274
+ newId,
4275
+ normalizedPayloadSchema,
4276
+ noteSchema,
4277
+ notesQuerySchema,
4278
+ notificationPrefsSchema,
4279
+ notificationsQuerySchema,
4280
+ notificationsReadSchema,
4281
+ packetElementSchema,
4282
+ packetQuerySchema,
4283
+ parseReportOptions,
4284
+ pingPayloadSchema,
4285
+ polishResponseSchema,
4286
+ projectBinCreateSchema,
4287
+ projectCreateSchema,
4288
+ projectDuplicateJobInputSchema,
4289
+ projectDuplicateSchema,
4290
+ projectFolderCreateSchema,
4291
+ projectFolderDeleteQuerySchema,
4292
+ projectFolderUpdateSchema,
4293
+ projectKindSchema,
4294
+ projectListQuerySchema,
4295
+ projectPurgeSchema,
4296
+ projectUpdateSchema,
4297
+ purchaseHandoffRedeemSchema,
4298
+ purchaseHandoffRequestSchema,
4299
+ purgeInputSchema,
4300
+ pushTokenSetSchema,
4301
+ reauthPayloadSchema,
4302
+ rejectPayloadSchema,
4303
+ reportCreateSchema,
4304
+ reportGetQuerySchema,
4305
+ reportKindInfos,
4306
+ reportRenderInputSchema,
4307
+ reportToCsv,
4308
+ requiredRoleFor,
4309
+ resolveBatchSchema,
4310
+ resolveOneQuerySchema,
4311
+ roleAtLeast,
4312
+ roleChangedPayloadSchema,
4313
+ roleSchema,
4314
+ scenePacketBodySchema,
4315
+ scenePacketSchema,
4316
+ scenesBatchRequestSchema,
4317
+ searchQuerySchema,
4318
+ serverShutdownPayloadSchema,
4319
+ shareLinkCreateSchema,
4320
+ shareLinkUpdateSchema,
4321
+ shareUnlockSchema,
4322
+ shotPacketBodySchema,
4323
+ shotPacketSchema,
4324
+ snapshotCommentAnchorSchema,
4325
+ snapshotCommentCreateSchema,
4326
+ snapshotCommentResolveSchema,
4327
+ snapshotCommentsQuerySchema,
4328
+ snapshotCreateSchema,
4329
+ snapshotForkSchema,
4330
+ snapshotNoteCreateSchema,
4331
+ snapshotOpenSchema,
4332
+ snapshotPrefsSchema,
4333
+ sourceParamOf,
4334
+ sourceParamSchema,
4335
+ sourceQuerySchema,
4336
+ sourceRefSchema,
4337
+ subscribeErrorPayloadSchema,
4338
+ subscribePayloadSchema,
4339
+ subscribedPayloadSchema,
4340
+ successResponse,
4341
+ suggestionAcceptSchema,
4342
+ suggestionDecideSchema,
4343
+ suggestionRejectSchema,
4344
+ syncFrameSchema,
4345
+ tagsQuerySchema,
4346
+ telemetryEventSchema,
4347
+ telemetryRequestSchema,
4348
+ templateCreateSchema,
4349
+ templateExportQuerySchema,
4350
+ templateGetQuerySchema,
4351
+ templateImportByIdSchema,
4352
+ templateImportSchema,
4353
+ templateListQuerySchema,
4354
+ templateThumbnailInputSchema,
4355
+ templateUpdateSchema,
4356
+ templateVersionCreateSchema,
4357
+ trashEmptySchema,
4358
+ unsubscribePayloadSchema,
4359
+ uploadStateRequestSchema,
4360
+ userMacroPatchSchema,
4361
+ userMacroSchema,
4362
+ userPreferencesPutSchema,
4363
+ userPreferencesSchema,
4364
+ versionRestoreAsCopySchema,
4365
+ watermarkLookupSchema,
4366
+ watermarkStyleSchema,
4367
+ workspaceActivityQuerySchema,
4368
+ workspaceAuditQuerySchema,
4369
+ workspaceContactInputSchema,
4370
+ workspaceContactPatchSchema,
4371
+ workspaceContactsCreateSchema,
4372
+ workspaceContactsQuerySchema,
4373
+ workspaceCreateSchema,
4374
+ workspaceDefaultsPutSchema,
4375
+ workspaceDefaultsSchema,
4376
+ workspaceDeleteSchema,
4377
+ workspaceDocumentsQuerySchema,
4378
+ workspaceKindSchema,
4379
+ workspaceTransferSchema,
4380
+ workspaceUpdateSchema,
4381
+ writingGoalInputSchema,
4382
+ writingGoalsPutSchema,
4383
+ writingSessionSchema,
4384
+ writingStatsQuerySchema
4385
+ };
4386
+ //# sourceMappingURL=index.js.map