@leaflow/sdk 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -492,10 +492,24 @@ export interface components {
492
492
  GrantResource: {
493
493
  admin: boolean;
494
494
  owner: boolean;
495
- /** @description 持有的全部自定义角色的权限并集,已去重排序 */
496
- permissions: string[] | null;
497
495
  /** @description 持有的角色编码,只用于展示 */
498
496
  roles: string[] | null;
497
+ /** @description 他全部策略编译出来的规则。**不要自己遍历它做判定**——拿它配上自己那份权限目录交给 pkg/rbac:那里面的顺序(所有者不可被 deny、deny 优先于管理员、带资源范围的规则不 参与项目级判定)每一条都对着一种会静默放行的写法。 */
498
+ rules: components["schemas"]["RuleResource"][] | null;
499
+ };
500
+ ResourceRefResource: {
501
+ /** @description 是字符串而不是 uuid:dns 的 zone 标识是一个域名,而且它根本不在 IAM 的库里。匹配 语义是 glob,所以 *.example.com 能表达一批子域名;uuid 和域名都不含 glob 元字符, 对它们来说这就是精确相等。 */
502
+ id: string;
503
+ /** @description 形如 compute:instance、dns:zone,和权限名同一个命名空间 */
504
+ type: string;
505
+ };
506
+ RuleResource: {
507
+ /** @enum {string} */
508
+ effect: "allow" | "deny";
509
+ /** @description 支持尾部通配(compute:instance.*),通配必须带服务前缀 */
510
+ permissions: string[] | null;
511
+ /** @description 为空表示这条规则在整个项目范围内成立;非空则表示它只在这些资源上成立,而那意味着 它回答不了项目级的问题。 */
512
+ resources: components["schemas"]["ResourceRefResource"][] | null;
499
513
  };
500
514
  ProjectAccessResource: {
501
515
  grant: components["schemas"]["GrantResource"];
@@ -32,6 +32,24 @@ export type ListMembersQuery = operations["list-members"]["parameters"]["query"]
32
32
  export type SetMemberRolesResult = operations["set-member-roles"]["responses"][200]["content"]["application/json"];
33
33
  /** `PUT /api/v1/members/{userId}/roles` 的请求体。 */
34
34
  export type SetMemberRolesBody = NonNullable<operations["set-member-roles"]["requestBody"]>["content"]["application/json"];
35
+ /** `PUT /api/v1/members/{userId}/permissions` 成功时的响应体。 */
36
+ export type SetMemberPermissionsResult = operations["set-member-permissions"]["responses"][200]["content"]["application/json"];
37
+ /** `PUT /api/v1/members/{userId}/permissions` 的请求体。 */
38
+ export type SetMemberPermissionsBody = NonNullable<operations["set-member-permissions"]["requestBody"]>["content"]["application/json"];
39
+ /** `GET /api/v1/policies` 成功时的响应体。 */
40
+ export type ListPoliciesResult = operations["list-policies"]["responses"][200]["content"]["application/json"];
41
+ /** `GET /api/v1/policies` 的查询参数。 */
42
+ export type ListPoliciesQuery = operations["list-policies"]["parameters"]["query"];
43
+ /** `POST /api/v1/policies` 成功时的响应体。 */
44
+ export type AttachPolicyResult = operations["attach-policy"]["responses"][201]["content"]["application/json"];
45
+ /** `POST /api/v1/policies` 的请求体。 */
46
+ export type AttachPolicyBody = NonNullable<operations["attach-policy"]["requestBody"]>["content"]["application/json"];
47
+ /** `GET /api/v1/policies/{policyId}` 成功时的响应体。 */
48
+ export type GetPolicyResult = operations["get-policy"]["responses"][200]["content"]["application/json"];
49
+ /** `PUT /api/v1/policies/{policyId}` 成功时的响应体。 */
50
+ export type UpdatePolicyResult = operations["update-policy"]["responses"][200]["content"]["application/json"];
51
+ /** `PUT /api/v1/policies/{policyId}` 的请求体。 */
52
+ export type UpdatePolicyBody = NonNullable<operations["update-policy"]["requestBody"]>["content"]["application/json"];
35
53
  /** `POST /api/v1/transfer-ownership` 成功时的响应体。 */
36
54
  export type TransferProjectOwnershipResult = operations["transfer-project-ownership"]["responses"][200]["content"]["application/json"];
37
55
  /** `POST /api/v1/transfer-ownership` 的请求体。 */
@@ -11,8 +11,8 @@ export interface paths {
11
11
  cookie?: never;
12
12
  };
13
13
  /**
14
- * 列出 IAM 自己声明的权限
15
- * @description **只有 IAM 这一份。** 别的服务的操作不在这里——权限目录由各个服务自己声明,IAM 认识它们就等于要跟着每个下游一起发版。
14
+ * 列出全平台可授予的权限
15
+ * @description 各服务在启动时把自己那份目录注册进 IAM(和 AddFinalizer 同一段代码),所以这里是一份汇总,不只是 IAM 自己那几条。**IAM 不用它做判定**——判定在各服务自己那边,拿 Grant 配它自己那份目录算;这份汇总只是让界面画得出勾选框,它落后一个版本只会让界面上少几条可选项,不会让判定出错。一个还没启动过的服务,它的权限不在这里。
16
16
  */
17
17
  get: operations["list-permissions"];
18
18
  put?: never;
@@ -190,6 +190,78 @@ export interface paths {
190
190
  patch?: never;
191
191
  trace?: never;
192
192
  };
193
+ "/api/v1/members/{userId}/permissions": {
194
+ parameters: {
195
+ query?: never;
196
+ header?: never;
197
+ path?: never;
198
+ cookie?: never;
199
+ };
200
+ get?: never;
201
+ /**
202
+ * 设置一个成员直挂的权限
203
+ * @description 整体替换基础策略上直挂的那些权限。直挂让「给某个人临时开一条」不必先造一个只有他一个人持有的角色,但它不会随角色调整而更新,所以它适合一次性的、说得出理由的授予——角色仍然是主要的组织方式。要 iam:members.manage。
204
+ */
205
+ put: operations["set-member-permissions"];
206
+ post?: never;
207
+ delete?: never;
208
+ options?: never;
209
+ head?: never;
210
+ patch?: never;
211
+ trace?: never;
212
+ };
213
+ "/api/v1/policies": {
214
+ parameters: {
215
+ query?: never;
216
+ header?: never;
217
+ path?: never;
218
+ cookie?: never;
219
+ };
220
+ /**
221
+ * 列出这个项目里的策略
222
+ * @description 在项目里就看得到,和成员列表同一条规则:谁被授了什么也是「这个项目有谁」的一部分。
223
+ */
224
+ get: operations["list-policies"];
225
+ put?: never;
226
+ /**
227
+ * 附加一条策略
228
+ * @description 它建的是**附加**策略——要么带资源范围,要么方向是 deny。一条不限资源的 allow 是基础策略,每个成员只有一条,改它走 set-member-roles 和 set-member-permissions。roles 里不能有 OWNER 或 ADMIN:它们是规则而不是权限集合,「限定在三台机器上的所有者」讲不通。要 iam:members.manage。
229
+ */
230
+ post: operations["attach-policy"];
231
+ delete?: never;
232
+ options?: never;
233
+ head?: never;
234
+ patch?: never;
235
+ trace?: never;
236
+ };
237
+ "/api/v1/policies/{policyId}": {
238
+ parameters: {
239
+ query?: never;
240
+ header?: never;
241
+ path?: never;
242
+ cookie?: never;
243
+ };
244
+ /**
245
+ * 查看一条策略
246
+ * @description 和 list-policies 同一条规则,在项目里就看得到:谁被授了什么也是「这个项目有谁」的一部分,读它不需要额外的权限。
247
+ */
248
+ get: operations["get-policy"];
249
+ /**
250
+ * 改一条策略
251
+ * @description 整体替换而不是逐字段改:resources、roles、permissions 各自整份覆盖,没发的那份就是空的——只有「这就是这条策略现在的全貌」这一种语义说得清一次写入到底收回了什么。改不动的是策略的**种类**:基础策略(不限资源的 allow)加不上资源范围,这个成员的角色就存在它上面,给它加个范围等于让他在别的资源上什么都不是;一条带范围的策略反过来也不能把范围清空变成基础策略,那个位置每个成员只有一条。要换种类就删了重建。要 iam:members.manage。
252
+ */
253
+ put: operations["update-policy"];
254
+ post?: never;
255
+ /**
256
+ * 摘掉一条策略
257
+ * @description 基础策略摘不掉——它是这个成员角色的落点,删了它这个人就不再持有任何角色,而「让他离开这个项目」是 remove-member 的事。要 iam:members.manage。
258
+ */
259
+ delete: operations["detach-policy"];
260
+ options?: never;
261
+ head?: never;
262
+ patch?: never;
263
+ trace?: never;
264
+ };
193
265
  "/api/v1/transfer-ownership": {
194
266
  parameters: {
195
267
  query?: never;
@@ -320,17 +392,43 @@ export interface components {
320
392
  name: string;
321
393
  /** @description 只有项目所有者能做,绑到自定义角色上也不会生效 */
322
394
  owner_only: boolean;
395
+ /** @description 这条权限的判定对象是哪类资源,空表示它是项目级的。**它不必等于操作对象本身**——compute 的 route 表上没有 project_id,隔离本来就经父网络传递,所以 compute:route.create 的判定对象是 compute:private_network。非空同时意味着这条权限可以被限定到具体实例;create 和 list 一律留空 */
396
+ resource_type: string;
397
+ };
398
+ ResourceTypeResource: {
399
+ description: string;
400
+ name: string;
401
+ };
402
+ CatalogResource: {
403
+ permissions: components["schemas"]["PermissionResource"][] | null;
404
+ /** @description 这个服务声明的资源类型。带资源范围的规则只落得到它们上面——一个没有被声明过的类型没人认得,限定在它上面的规则谁都判不出来 */
405
+ resource_types: components["schemas"]["ResourceTypeResource"][] | null;
406
+ service: string;
407
+ };
408
+ CatalogListResponseBody: {
409
+ items: components["schemas"]["CatalogResource"][] | null;
410
+ };
411
+ ResourceRefResource: {
412
+ /** @description 是字符串不是 uuid:dns 的 zone 标识是域名,而且不在 IAM 库里。匹配语义是 glob,所以 *.example.com 能表达一批子域名;uuid 和域名都不含 glob 元字符,对它们来说就是精确相等 */
413
+ id: string;
414
+ /** @description 形如 compute:instance、dns:zone,和权限名同一个命名空间 */
415
+ type: string;
323
416
  };
324
- PermissionListResponseBody: {
325
- items: components["schemas"]["PermissionResource"][] | null;
417
+ RuleResource: {
418
+ /** @enum {string} */
419
+ effect: "allow" | "deny";
420
+ /** @description 权限名,支持尾部通配(compute:instance.*)。通配必须带服务前缀——一条光秃秃的 * 会把日后新上线的服务的操作也一起授出去,而那件事发生的时候没有任何人在场 */
421
+ permissions: string[] | null;
422
+ /** @description 为空表示整个项目范围;非空表示这条规则只在这些资源上成立,而那意味着它回答不了项目级的问题 */
423
+ resources: components["schemas"]["ResourceRefResource"][] | null;
326
424
  };
327
425
  GrantResource: {
328
426
  admin: boolean;
329
427
  owner: boolean;
330
- /** @description 持有的全部自定义角色的权限并集,已去重排序 */
331
- permissions: string[] | null;
332
428
  /** @description 持有的角色编码,只用于展示 */
333
429
  roles: string[] | null;
430
+ /** @description 他全部策略编译出来的规则。**不要自己遍历它做判定**——拿它配上自己那份权限目录交给 pkg/rbac:owner 不可被 deny、deny 优先于 admin、带资源范围的规则不参与项目级判定,那里面的顺序每一条都对着一种会静默放行的写法 */
431
+ rules: components["schemas"]["RuleResource"][] | null;
334
432
  };
335
433
  ProjectResource: {
336
434
  ban_reason: string;
@@ -470,6 +568,63 @@ export interface components {
470
568
  /** @description 这个人应当持有的**全部**角色编码。OWNER 不能出现在这里 */
471
569
  roles: string[] | null;
472
570
  };
571
+ SetMemberPermissionsRequestBody: {
572
+ /** @description 这个人应当直挂的**全部**权限名,整体替换。角色给的那些不在这里,也不会被这次写入碰到 */
573
+ permissions: string[] | null;
574
+ };
575
+ PolicyResource: {
576
+ /** @description 基础策略是方向 allow、不限资源的那一条,每个成员恰好一条,装的是他的常规角色。改它走 PUT /members/{userId}/roles 和 PUT /members/{userId}/permissions */
577
+ base: boolean;
578
+ /** Format: date-time */
579
+ created_at: string;
580
+ /** @description 给人看的理由。一条附加策略事后最难回答的是「当初为什么开这一条」 */
581
+ description: string;
582
+ /** @enum {string} */
583
+ effect: "allow" | "deny";
584
+ /** Format: uuid */
585
+ id: string;
586
+ /** @description 直挂在这个人身上的权限名,不经过角色 */
587
+ permissions: string[] | null;
588
+ /** @description 为空表示整个项目范围 */
589
+ resources: components["schemas"]["ResourceRefResource"][] | null;
590
+ /** @description 这条策略带上的角色编码 */
591
+ roles: string[] | null;
592
+ /** Format: date-time */
593
+ updated_at: string;
594
+ user_id: string;
595
+ };
596
+ PolicyListResponseBody: {
597
+ items: components["schemas"]["PolicyResource"][] | null;
598
+ };
599
+ AttachPolicyRequestBody: {
600
+ /** @description 给人看的理由。一条附加策略事后最难回答的是「当初为什么开这一条」 */
601
+ description?: string;
602
+ /** @enum {string} */
603
+ effect: "allow" | "deny";
604
+ /** @description 直挂的权限名,用它就不必为一个人临时造一个只有他持有的角色 */
605
+ permissions?: string[] | null;
606
+ /** @description 这条策略只在这些资源上成立。留空只有配合 deny 才讲得通——一条不限资源的 allow 是基础策略,那一条已经有了 */
607
+ resources?: components["schemas"]["ResourceRefResource"][] | null;
608
+ /** @description 必须是这个项目已经定义的角色。OWNER 和 ADMIN 不行——它们是规则而不是权限集合,「限定在三台机器上的所有者」讲不通 */
609
+ roles?: string[] | null;
610
+ /** @description 必须已经是这个项目的成员 */
611
+ user_id: string;
612
+ };
613
+ UpdatePolicyRequestBody: {
614
+ /** @description 给人看的理由。它和这次改动一起替换,不然留下来的会是一句解释着上一个版本的话 */
615
+ description?: string;
616
+ /**
617
+ * @description 必须和这条策略当前的方向一致。方向改不动——那不是「改一条策略」,是一次意思完全相反的授权决定,改它的人多半以为自己在收紧,而读这行数据的下一个人看到的是一条方向和当初授予时不同、说明文字却还是旧的策略。仍然要求发这个字段而不是干脆不收,是因为整体替换的语义是「这就是这条策略现在的全貌」:少一个字段的话,调用方以为自己把 deny 改成了 allow,而服务端默默忽略了它。不一致时返回 PROJECT_POLICY_EFFECT_IMMUTABLE
618
+ * @enum {string}
619
+ */
620
+ effect: "allow" | "deny";
621
+ /** @description 直挂的权限名,整份替换。没列进来的就是被收回了——它不是往上加一条 */
622
+ permissions?: string[] | null;
623
+ /** @description 这条策略的资源范围,整份替换。范围内容能改,有没有范围改不了:基础策略加不上范围,带范围的也清不空——清空之后它就是基础策略的形状,而那个位置每个成员只有一条 */
624
+ resources?: components["schemas"]["ResourceRefResource"][] | null;
625
+ /** @description 必须是这个项目已经定义的角色,整份替换。和挂上去那次一样不能有 OWNER 或 ADMIN */
626
+ roles?: string[] | null;
627
+ };
473
628
  TransferOwnershipRequestBody: {
474
629
  /** @description 接手的人必须已经是这个项目的成员 */
475
630
  to_user_id: string;
@@ -584,7 +739,7 @@ export interface operations {
584
739
  [name: string]: unknown;
585
740
  };
586
741
  content: {
587
- "application/json": components["schemas"]["PermissionListResponseBody"];
742
+ "application/json": components["schemas"]["CatalogListResponseBody"];
588
743
  };
589
744
  };
590
745
  /** @description Error */
@@ -947,6 +1102,201 @@ export interface operations {
947
1102
  };
948
1103
  };
949
1104
  };
1105
+ "set-member-permissions": {
1106
+ parameters: {
1107
+ query?: never;
1108
+ header?: never;
1109
+ path: {
1110
+ userId: string;
1111
+ };
1112
+ cookie?: never;
1113
+ };
1114
+ requestBody: {
1115
+ content: {
1116
+ "application/json": components["schemas"]["SetMemberPermissionsRequestBody"];
1117
+ };
1118
+ };
1119
+ responses: {
1120
+ /** @description OK */
1121
+ 200: {
1122
+ headers: {
1123
+ [name: string]: unknown;
1124
+ };
1125
+ content: {
1126
+ "application/json": components["schemas"]["PolicyResource"];
1127
+ };
1128
+ };
1129
+ /** @description Error */
1130
+ default: {
1131
+ headers: {
1132
+ [name: string]: unknown;
1133
+ };
1134
+ content: {
1135
+ "application/json": components["schemas"]["Error"];
1136
+ };
1137
+ };
1138
+ };
1139
+ };
1140
+ "list-policies": {
1141
+ parameters: {
1142
+ query?: {
1143
+ /** @description 只看这个人身上的。不传表示整个项目的 */
1144
+ userId?: string;
1145
+ };
1146
+ header?: never;
1147
+ path?: never;
1148
+ cookie?: never;
1149
+ };
1150
+ requestBody?: never;
1151
+ responses: {
1152
+ /** @description OK */
1153
+ 200: {
1154
+ headers: {
1155
+ [name: string]: unknown;
1156
+ };
1157
+ content: {
1158
+ "application/json": components["schemas"]["PolicyListResponseBody"];
1159
+ };
1160
+ };
1161
+ /** @description Error */
1162
+ default: {
1163
+ headers: {
1164
+ [name: string]: unknown;
1165
+ };
1166
+ content: {
1167
+ "application/json": components["schemas"]["Error"];
1168
+ };
1169
+ };
1170
+ };
1171
+ };
1172
+ "attach-policy": {
1173
+ parameters: {
1174
+ query?: never;
1175
+ header?: never;
1176
+ path?: never;
1177
+ cookie?: never;
1178
+ };
1179
+ requestBody: {
1180
+ content: {
1181
+ "application/json": components["schemas"]["AttachPolicyRequestBody"];
1182
+ };
1183
+ };
1184
+ responses: {
1185
+ /** @description Created */
1186
+ 201: {
1187
+ headers: {
1188
+ [name: string]: unknown;
1189
+ };
1190
+ content: {
1191
+ "application/json": components["schemas"]["PolicyResource"];
1192
+ };
1193
+ };
1194
+ /** @description Error */
1195
+ default: {
1196
+ headers: {
1197
+ [name: string]: unknown;
1198
+ };
1199
+ content: {
1200
+ "application/json": components["schemas"]["Error"];
1201
+ };
1202
+ };
1203
+ };
1204
+ };
1205
+ "get-policy": {
1206
+ parameters: {
1207
+ query?: never;
1208
+ header?: never;
1209
+ path: {
1210
+ policyId: string;
1211
+ };
1212
+ cookie?: never;
1213
+ };
1214
+ requestBody?: never;
1215
+ responses: {
1216
+ /** @description OK */
1217
+ 200: {
1218
+ headers: {
1219
+ [name: string]: unknown;
1220
+ };
1221
+ content: {
1222
+ "application/json": components["schemas"]["PolicyResource"];
1223
+ };
1224
+ };
1225
+ /** @description Error */
1226
+ default: {
1227
+ headers: {
1228
+ [name: string]: unknown;
1229
+ };
1230
+ content: {
1231
+ "application/json": components["schemas"]["Error"];
1232
+ };
1233
+ };
1234
+ };
1235
+ };
1236
+ "update-policy": {
1237
+ parameters: {
1238
+ query?: never;
1239
+ header?: never;
1240
+ path: {
1241
+ policyId: string;
1242
+ };
1243
+ cookie?: never;
1244
+ };
1245
+ requestBody: {
1246
+ content: {
1247
+ "application/json": components["schemas"]["UpdatePolicyRequestBody"];
1248
+ };
1249
+ };
1250
+ responses: {
1251
+ /** @description OK */
1252
+ 200: {
1253
+ headers: {
1254
+ [name: string]: unknown;
1255
+ };
1256
+ content: {
1257
+ "application/json": components["schemas"]["PolicyResource"];
1258
+ };
1259
+ };
1260
+ /** @description Error */
1261
+ default: {
1262
+ headers: {
1263
+ [name: string]: unknown;
1264
+ };
1265
+ content: {
1266
+ "application/json": components["schemas"]["Error"];
1267
+ };
1268
+ };
1269
+ };
1270
+ };
1271
+ "detach-policy": {
1272
+ parameters: {
1273
+ query?: never;
1274
+ header?: never;
1275
+ path: {
1276
+ policyId: string;
1277
+ };
1278
+ cookie?: never;
1279
+ };
1280
+ requestBody?: never;
1281
+ responses: {
1282
+ /** @description No Content */
1283
+ 204: {
1284
+ headers: {
1285
+ [name: string]: unknown;
1286
+ };
1287
+ content?: never;
1288
+ };
1289
+ /** @description Error */
1290
+ default: {
1291
+ headers: {
1292
+ [name: string]: unknown;
1293
+ };
1294
+ content: {
1295
+ "application/json": components["schemas"]["Error"];
1296
+ };
1297
+ };
1298
+ };
1299
+ };
950
1300
  "transfer-project-ownership": {
951
1301
  parameters: {
952
1302
  query?: never;
@@ -34,6 +34,14 @@ export type GetNotificationPreferencesResult = operations["get-notification-pref
34
34
  export type UpdateNotificationPreferencesResult = operations["update-notification-preferences"]["responses"][200]["content"]["application/json"];
35
35
  /** `PATCH /api/v1/preferences` 的请求体。 */
36
36
  export type UpdateNotificationPreferencesBody = NonNullable<operations["update-notification-preferences"]["requestBody"]>["content"]["application/json"];
37
+ /** `POST /api/v1/preferences/email-override/code` 成功时的响应体。 */
38
+ export type SendEmailOverrideCodeResult = operations["send-email-override-code"]["responses"][200]["content"]["application/json"];
39
+ /** `DELETE /api/v1/preferences/email-override/code` 成功时的响应体。 */
40
+ export type CancelEmailOverrideResult = operations["cancel-email-override"]["responses"][200]["content"]["application/json"];
41
+ /** `POST /api/v1/preferences/email-override/code/confirm` 成功时的响应体。 */
42
+ export type ConfirmEmailOverrideResult = operations["confirm-email-override"]["responses"][200]["content"]["application/json"];
43
+ /** `POST /api/v1/preferences/email-override/code/confirm` 的请求体。 */
44
+ export type ConfirmEmailOverrideBody = NonNullable<operations["confirm-email-override"]["requestBody"]>["content"]["application/json"];
37
45
  /** `GET /api/v1/preferences/types` 成功时的响应体。 */
38
46
  export type ListTypePreferencesResult = operations["list-type-preferences"]["responses"][200]["content"]["application/json"];
39
47
  /** `GET /api/v1/preferences/types` 的查询参数。 */
@@ -275,13 +275,80 @@ export interface paths {
275
275
  * Update your delivery preferences
276
276
  * @description Fields that are omitted are left alone.
277
277
  *
278
- * Setting `email_override` starts verification of that address: email continues to be
279
- * delivered to the address on the account until the new one is confirmed. Setting it to null
280
- * returns delivery to the account address.
278
+ * Setting `email_override` records it as the pending address; nothing is sent to it until
279
+ * `send-email-override-code` is called, and email keeps going where it went before. Setting
280
+ * it to null clears the pending address and any address in use, returning delivery to the
281
+ * account address.
282
+ *
283
+ * An address is never used until somebody proves they can read it. Without that, anybody
284
+ * could point their own notifications at a stranger's mailbox, and the stranger would get
285
+ * mail about an account they have never heard of.
281
286
  */
282
287
  patch: operations["update-notification-preferences"];
283
288
  trace?: never;
284
289
  };
290
+ "/api/v1/preferences/email-override/code": {
291
+ parameters: {
292
+ query?: never;
293
+ header?: never;
294
+ path?: never;
295
+ cookie?: never;
296
+ };
297
+ get?: never;
298
+ put?: never;
299
+ /**
300
+ * Send a code to the pending address
301
+ * @description Emails a short code to the address waiting to be confirmed. That address receives this and
302
+ * nothing else; every other notification keeps going where it went before.
303
+ *
304
+ * The code stops working after a few minutes, and a wrong one can only be tried a handful of
305
+ * times before it is thrown away and a new one has to be sent. Both limits exist for the same
306
+ * reason: a six digit code is guessable if it lives forever and can be tried forever.
307
+ *
308
+ * Sending is rate limited per account and per destination address. The second limit is the one
309
+ * that matters to somebody who never asked to be involved: without it, this operation is a way
310
+ * to make the platform mail a stranger repeatedly.
311
+ */
312
+ post: operations["send-email-override-code"];
313
+ /**
314
+ * Give up on the pending address
315
+ * @description Drops the pending address and the code that was sent to it. Nothing else changes: an address
316
+ * that was already confirmed keeps receiving email.
317
+ *
318
+ * It exists because starting this and then changing your mind is ordinary, and the alternative
319
+ * is a settings page that shows an address waiting to be confirmed forever.
320
+ */
321
+ delete: operations["cancel-email-override"];
322
+ options?: never;
323
+ head?: never;
324
+ patch?: never;
325
+ trace?: never;
326
+ };
327
+ "/api/v1/preferences/email-override/code/confirm": {
328
+ parameters: {
329
+ query?: never;
330
+ header?: never;
331
+ path?: never;
332
+ cookie?: never;
333
+ };
334
+ get?: never;
335
+ put?: never;
336
+ /**
337
+ * Confirm the pending address with its code
338
+ * @description A correct code moves the pending address into use: email starts going there instead of the
339
+ * account address, and the pending slot is emptied.
340
+ *
341
+ * A wrong code counts against the attempts on that code. Running out of attempts throws the
342
+ * code away, and a new one has to be sent; the pending address itself is kept, so nobody
343
+ * loses their place by mistyping.
344
+ */
345
+ post: operations["confirm-email-override"];
346
+ delete?: never;
347
+ options?: never;
348
+ head?: never;
349
+ patch?: never;
350
+ trace?: never;
351
+ };
285
352
  "/api/v1/preferences/types": {
286
353
  parameters: {
287
354
  query?: never;
@@ -756,11 +823,26 @@ export interface components {
756
823
  PreferencesResource: {
757
824
  /** @description Where email is delivered right now, which is the account address unless a verified override replaces it */
758
825
  email_address: string;
759
- /** @description An address to receive email instead of the account address; null when the account address is used */
826
+ /**
827
+ * @description The address email goes to instead of the account address; null when the account address
828
+ * is used. Only ever an address that was confirmed by code
829
+ */
760
830
  email_override: string | null;
831
+ /**
832
+ * @description An address waiting to be confirmed; null when nothing is waiting. It receives nothing but
833
+ * the code, and the address in `email_override` keeps being used until this one is
834
+ * confirmed
835
+ */
836
+ email_override_pending: string | null;
837
+ /**
838
+ * Format: date-time
839
+ * @description When the code last sent to the pending address stops working; null when no code is
840
+ * outstanding, in which case a new one has to be sent before it can be confirmed
841
+ */
842
+ email_override_pending_expires_at: string | null;
761
843
  /**
762
844
  * Format: date-time
763
- * @description When the override was confirmed; null while it is pending, during which email still goes to the account address
845
+ * @description When the address in `email_override` was confirmed; null when there is no override
764
846
  */
765
847
  email_override_verified_at: string | null;
766
848
  /** @description The language notifications are written in, as an IETF language tag */
@@ -768,11 +850,36 @@ export interface components {
768
850
  };
769
851
  /** @description Fields that are omitted are left alone. */
770
852
  UpdatePreferencesRequestBody: {
771
- /** @description An address to receive email instead of the account address. Null returns delivery to the account address */
853
+ /**
854
+ * @description An address to receive email instead of the account address. It is stored as pending and
855
+ * receives nothing until it is confirmed by code, so the address in use does not change
856
+ * here. Null clears both the pending address and the one in use, returning delivery to
857
+ * the account address
858
+ */
772
859
  email_override?: string | null;
773
860
  /** @description An IETF language tag. A language that is not supported is rejected rather than approximated */
774
861
  locale?: string;
775
862
  };
863
+ /** @description What was sent, and when the next one may be asked for. */
864
+ EmailOverrideCodeResource: {
865
+ /** @description Where the code went. It is the pending address, echoed so a client can show it */
866
+ address: string;
867
+ /**
868
+ * Format: date-time
869
+ * @description When this code stops working
870
+ */
871
+ expires_at: string;
872
+ /**
873
+ * Format: date-time
874
+ * @description The earliest another code may be asked for. Asking sooner is refused rather than
875
+ * silently ignored, so a client can show the wait instead of a failure
876
+ */
877
+ resend_available_at: string;
878
+ };
879
+ ConfirmEmailOverrideRequestBody: {
880
+ /** @description The code from the email, exactly as it appears there */
881
+ code: string;
882
+ };
776
883
  TypePreferenceResource: {
777
884
  /** @description Where this type is delivered */
778
885
  channels: components["schemas"]["NotificationChannel"][];
@@ -1435,6 +1542,97 @@ export interface operations {
1435
1542
  };
1436
1543
  };
1437
1544
  };
1545
+ "send-email-override-code": {
1546
+ parameters: {
1547
+ query?: never;
1548
+ header?: never;
1549
+ path?: never;
1550
+ cookie?: never;
1551
+ };
1552
+ requestBody?: never;
1553
+ responses: {
1554
+ /** @description OK */
1555
+ 200: {
1556
+ headers: {
1557
+ [name: string]: unknown;
1558
+ };
1559
+ content: {
1560
+ "application/json": components["schemas"]["EmailOverrideCodeResource"];
1561
+ };
1562
+ };
1563
+ /** @description Error */
1564
+ default: {
1565
+ headers: {
1566
+ [name: string]: unknown;
1567
+ };
1568
+ content: {
1569
+ "application/json": components["schemas"]["Error"];
1570
+ };
1571
+ };
1572
+ };
1573
+ };
1574
+ "cancel-email-override": {
1575
+ parameters: {
1576
+ query?: never;
1577
+ header?: never;
1578
+ path?: never;
1579
+ cookie?: never;
1580
+ };
1581
+ requestBody?: never;
1582
+ responses: {
1583
+ /** @description OK */
1584
+ 200: {
1585
+ headers: {
1586
+ [name: string]: unknown;
1587
+ };
1588
+ content: {
1589
+ "application/json": components["schemas"]["PreferencesResource"];
1590
+ };
1591
+ };
1592
+ /** @description Error */
1593
+ default: {
1594
+ headers: {
1595
+ [name: string]: unknown;
1596
+ };
1597
+ content: {
1598
+ "application/json": components["schemas"]["Error"];
1599
+ };
1600
+ };
1601
+ };
1602
+ };
1603
+ "confirm-email-override": {
1604
+ parameters: {
1605
+ query?: never;
1606
+ header?: never;
1607
+ path?: never;
1608
+ cookie?: never;
1609
+ };
1610
+ requestBody: {
1611
+ content: {
1612
+ "application/json": components["schemas"]["ConfirmEmailOverrideRequestBody"];
1613
+ };
1614
+ };
1615
+ responses: {
1616
+ /** @description OK */
1617
+ 200: {
1618
+ headers: {
1619
+ [name: string]: unknown;
1620
+ };
1621
+ content: {
1622
+ "application/json": components["schemas"]["PreferencesResource"];
1623
+ };
1624
+ };
1625
+ /** @description Error */
1626
+ default: {
1627
+ headers: {
1628
+ [name: string]: unknown;
1629
+ };
1630
+ content: {
1631
+ "application/json": components["schemas"]["Error"];
1632
+ };
1633
+ };
1634
+ };
1635
+ };
1438
1636
  "list-type-preferences": {
1439
1637
  parameters: {
1440
1638
  query?: {
@@ -36,8 +36,8 @@ export type ListTicketCategoriesQuery = operations["list-ticket-categories"]["pa
36
36
  export type UploadAttachmentResult = operations["upload-attachment"]["responses"][201]["content"]["application/json"];
37
37
  /** `POST /api/v1/attachments` 的查询参数。 */
38
38
  export type UploadAttachmentQuery = operations["upload-attachment"]["parameters"]["query"];
39
- /** `GET /api/v1/attachments/{attachmentId}/download-url` 成功时的响应体。 */
40
- export type DescribeAttachmentDownloadResult = operations["describe-attachment-download"]["responses"][200]["content"]["application/json"];
39
+ /** `GET /api/v1/attachments/{attachmentId}/content` 的查询参数。 */
40
+ export type DownloadAttachmentQuery = operations["download-attachment"]["parameters"]["query"];
41
41
  /** `GET /api/v1/maintenances` 成功时的响应体。 */
42
42
  export type ListMaintenancesResult = operations["list-maintenances"]["responses"][200]["content"]["application/json"];
43
43
  /** `GET /api/v1/maintenances` 的查询参数。 */
@@ -202,7 +202,7 @@ export interface paths {
202
202
  patch?: never;
203
203
  trace?: never;
204
204
  };
205
- "/api/v1/attachments/{attachmentId}/download-url": {
205
+ "/api/v1/attachments/{attachmentId}/content": {
206
206
  parameters: {
207
207
  query?: never;
208
208
  header?: never;
@@ -210,13 +210,16 @@ export interface paths {
210
210
  cookie?: never;
211
211
  };
212
212
  /**
213
- * Get a download address for an attachment
214
- * @description Returns a temporary address that serves the file. The address expires; request a new one
215
- * rather than storing it.
213
+ * Download an attachment
214
+ * @description Serves the file itself. The bytes are proxied by this API; the object store is not reachable
215
+ * from outside, and no address to it is ever handed out.
216
216
  *
217
- * Returns 404 for an attachment uploaded in another project.
217
+ * `Content-Type` is the type determined from the content at upload time, not the one the
218
+ * client claimed. `Content-Disposition` is `attachment` unless `inline` is requested **and**
219
+ * the content type is one that can be rendered safely, in which case it is `inline`. Asking
220
+ * for `inline` on anything else still yields a download.
218
221
  */
219
- get: operations["describe-attachment-download"];
222
+ get: operations["download-attachment"];
220
223
  put?: never;
221
224
  post?: never;
222
225
  delete?: never;
@@ -469,15 +472,6 @@ export interface components {
469
472
  status: components["schemas"]["MaintenanceStatus"];
470
473
  title: string;
471
474
  };
472
- AttachmentDownloadResource: {
473
- /**
474
- * Format: date-time
475
- * @description After this moment the address stops working; request a new one
476
- */
477
- expires_at: string;
478
- /** @description A temporary address serving the file */
479
- url: string;
480
- };
481
475
  CreateTicketRequestBody: {
482
476
  /** @description Attachments to reference from the first message. Each must have been uploaded in this project and not yet referenced */
483
477
  attachment_ids?: string[];
@@ -940,9 +934,12 @@ export interface operations {
940
934
  };
941
935
  };
942
936
  };
943
- "describe-attachment-download": {
937
+ "download-attachment": {
944
938
  parameters: {
945
- query?: never;
939
+ query?: {
940
+ /** @description Render in the browser instead of downloading, where the content type allows it */
941
+ inline?: boolean;
942
+ };
946
943
  header?: never;
947
944
  path: {
948
945
  attachmentId: string;
@@ -954,10 +951,12 @@ export interface operations {
954
951
  /** @description OK */
955
952
  200: {
956
953
  headers: {
954
+ /** @description `inline` when the content type can be rendered safely and `inline` was asked for, `attachment` otherwise. Carries the original file name */
955
+ "Content-Disposition"?: string;
957
956
  [name: string]: unknown;
958
957
  };
959
958
  content: {
960
- "application/json": components["schemas"]["AttachmentDownloadResource"];
959
+ "*/*": string;
961
960
  };
962
961
  };
963
962
  /** @description Error */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leaflow/sdk",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "Leaflow 平台 API 的 TypeScript SDK",
5
5
  "license": "MIT",
6
6
  "repository": {