@oj-module/cli 0.2.50 → 0.2.54

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.
Files changed (130) hide show
  1. package/devkit/SKILL.md +2 -2
  2. package/devkit/manual.md +32 -25
  3. package/devkit/wecom-internal-app.md +111 -4
  4. package/package.json +2 -2
  5. package/shell-dist/assets/{index-FYt9CjFd.js → index-e_Mp2mb5.js} +5 -1
  6. package/shell-dist/assets/runtime.js +29 -5
  7. package/shell-dist/index.html +9 -12
  8. package/shell-dist/versions.json +3 -3
  9. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  10. package/src/build.ts +19 -2
  11. package/src/config.ts +9 -0
  12. package/src/contract/__tests__/client-path.test.ts +30 -0
  13. package/src/contract/__tests__/emit-client.test.ts +18 -0
  14. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  15. package/src/contract/check.ts +5 -5
  16. package/src/contract/client-path.ts +30 -0
  17. package/src/contract/emit-client.ts +18 -4
  18. package/src/contract/handler-scan.ts +40 -3
  19. package/src/contract/run.ts +5 -3
  20. package/src/init.ts +19 -1
  21. package/src/shared-deps.ts +27 -9
  22. package/src/site-base.ts +1 -1
  23. package/templates/README.md +40 -44
  24. package/templates/init.include.yaml +15 -14
  25. package/templates/openapi.json +3422 -1955
  26. package/templates/src/_platform/api/_shared/service/domain/users.ts +40 -4
  27. package/templates/src/_platform/api/contacts/api.ts +29 -1
  28. package/templates/src/_platform/api/contacts/detail/api.ts +25 -1
  29. package/templates/src/_platform/api/solutions/api.ts +60 -1
  30. package/templates/src/_platform/web/__client/api.schemas.ts +99 -35
  31. package/templates/src/_platform/web/__client/api.ts +103 -59
  32. package/templates/src/app/web/entry.ts +77 -0
  33. package/templates/src/{home/web → app/web/home}/__client/api.ts +5 -2
  34. package/templates/src/app/web/home/routes.ts +41 -0
  35. package/templates/src/{login/web → app/web/login}/pages/login.tsx +54 -24
  36. package/templates/src/app/web/login/routes.ts +71 -0
  37. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  38. package/templates/src/{notification/web → app/web/notification}/__client/api.ts +67 -20
  39. package/templates/src/app/web/notification/routes.ts +70 -0
  40. package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.ts +5 -2
  41. package/templates/src/app/web/personal-center/routes.ts +49 -0
  42. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +28 -22
  43. package/templates/src/{system/web → app/web/system}/__client/api.ts +103 -58
  44. package/templates/src/{system/web → app/web/system}/locales/en-US.json +14 -0
  45. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +14 -0
  46. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +5 -2
  47. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +14 -3
  48. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +139 -1
  49. package/templates/src/app/web/system/routes.ts +97 -0
  50. package/templates/src/auth/api/_shared/wecom/admin.ts +4 -0
  51. package/templates/src/auth/api/_shared/wecom/client.ts +9 -2
  52. package/templates/src/auth/api/_shared/wecom/events.ts +3 -0
  53. package/templates/src/auth/api/_shared/wecom/tenant-admin.ts +194 -0
  54. package/templates/src/auth/api/api.ts +59 -0
  55. package/templates/src/auth/api/ldap-login/api.ts +37 -1
  56. package/templates/src/auth/api/wecom/admin/tenant-admin/api.ts +41 -0
  57. package/templates/src/auth/api/wecom/admin/tenant-admin/password/api.ts +66 -0
  58. package/templates/src/auth/api/wecom/api.ts +23 -0
  59. package/templates/src/auth/web/__client/api.schemas.ts +133 -13
  60. package/templates/src/auth/web/__client/api.ts +129 -31
  61. package/templates/src/notification/api/api.ts +17 -0
  62. package/templates/src/notification/api/del/api.ts +2 -0
  63. package/templates/src/system/api/roles/api.ts +3 -0
  64. package/templates/src/system/api/roles/bindings/api.ts +3 -0
  65. package/templates/src/system/api/settings/api.ts +6 -3
  66. package/templates/src/system/api/users/forbidden/api.ts +3 -0
  67. package/templates/src/system/api/users/password/api.ts +3 -0
  68. package/templates/src/system/api/users/roles/api.ts +3 -0
  69. package/templates/src/web/api/get-async-routes/api.ts +16 -1
  70. package/templates/src/web/api/hello/api.ts +13 -1
  71. package/templates/src/web/api/user-info/api.ts +21 -1
  72. package/templates/src/web/web/__client/api.schemas.ts +28 -0
  73. package/templates/src/web/web/__client/api.ts +33 -9
  74. package/templates/tsconfig.paths.json +0 -1
  75. package/templates/web.config.ts +22 -17
  76. package/vendor/host-versions.json +3 -3
  77. package/shell-dist/assets/echarts-charts.js +0 -58571
  78. package/shell-dist/assets/echarts-features.js +0 -24057
  79. package/shell-dist/assets/echarts-for-react.js +0 -704
  80. package/shell-dist/assets/echarts.js +0 -82916
  81. package/shell-dist/assets/editor.js +0 -161457
  82. package/shell-dist/assets/vditor-index-css.js +0 -7
  83. package/shell-dist/assets/vditor.js +0 -15223
  84. package/templates/src/demo/api/manifest.yaml +0 -5
  85. package/templates/src/demo/api/migrations/0001__create_todos.sql +0 -9
  86. package/templates/src/demo/api/migrations/0002__tenant.mysql.sql +0 -2
  87. package/templates/src/demo/api/migrations/0002__tenant.sql +0 -2
  88. package/templates/src/demo/api/schema.yaml +0 -12
  89. package/templates/src/demo/api/seed.sql +0 -5
  90. package/templates/src/demo/api/todos/api.ts +0 -59
  91. package/templates/src/demo/web/__client/api.schemas.ts +0 -20
  92. package/templates/src/demo/web/__client/api.ts +0 -89
  93. package/templates/src/demo/web/entry.ts +0 -42
  94. package/templates/src/demo/web/locales/en-US.json +0 -10
  95. package/templates/src/demo/web/locales/zh-CN.json +0 -10
  96. package/templates/src/demo/web/pages/index.tsx +0 -45
  97. package/templates/src/home/web/entry.ts +0 -49
  98. package/templates/src/login/web/entry.ts +0 -79
  99. package/templates/src/notification/web/entry.ts +0 -84
  100. package/templates/src/personal-center/web/entry.ts +0 -60
  101. package/templates/src/system/web/entry.ts +0 -108
  102. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  103. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  104. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  105. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  106. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  107. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  108. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  109. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  110. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  111. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  112. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  113. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  114. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  115. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  116. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  117. /package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +0 -0
  118. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  119. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  120. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  121. /package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +0 -0
  122. /package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +0 -0
  123. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  124. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  125. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  126. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
  127. /package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +0 -0
  128. /package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +0 -0
  129. /package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +0 -0
  130. /package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +0 -0
@@ -59,13 +59,30 @@ function assertNotSelf(actorId: number, targetUid: number): void {
59
59
  fail(400, "cannot operate on yourself");
60
60
  }
61
61
 
62
- /** AC1-8:成员资格(superadmin/admin 分配)仅 superadmin 可动。 */
62
+ /**
63
+ * AC1-8(已修订):成员资格提权守卫——按**被授予的角色**分层,不再一刀切。
64
+ *
65
+ * · `superadmin` —— 仍严格只许 superadmin 授。它是**平台级**角色:握着它就能进
66
+ * /system/wecom 那类跨租户页面(listApps 列出**所有企业**的档案),一旦租户内可授
67
+ * 即为跨租户越权,故这条不因下面的修订而放宽。
68
+ * · `admin` —— 允许本租户内握有 `admin` 或 `superadmin` 的人任命。
69
+ * 判定与写入两侧都被租户作用域约束:`rolesOf()` 读 sys_user_role 走请求租户注入,
70
+ * 下面的写入同样带 tenant_id,所以租户管理员只能影响**自己租户**,不会越到别家。
71
+ * 动机:企业租户是隐式具象化的,平台 superadmin 因租户隔离查不到企业租户的用户
72
+ * (实测 404),若 admin 任命也只许 superadmin,企业租户就永远配不出管理员。
73
+ * · 其余角色(editor/viewer 等)—— 不校验,保持原行为。
74
+ */
63
75
  async function assertMembershipAllowed(actorId: number, nextRoles: string[]): Promise<void> {
64
76
  if (!nextRoles.includes("superadmin") && !nextRoles.includes("admin"))
65
77
  return;
66
78
  const actorRoles = await rolesOf(Number(actorId));
67
- if (!actorRoles.includes("superadmin"))
68
- fail(403, "membership management requires superadmin");
79
+ const isSuper = actorRoles.includes("superadmin");
80
+ // 提权到平台级:只许 superadmin
81
+ if (nextRoles.includes("superadmin") && !isSuper)
82
+ fail(403, "granting superadmin requires superadmin");
83
+ // 任命本租户管理员:本租户的 admin 或 superadmin 均可
84
+ if (nextRoles.includes("admin") && !isSuper && !actorRoles.includes("admin"))
85
+ fail(403, "membership management requires admin or superadmin");
69
86
  }
70
87
 
71
88
  export async function listUsers(): Promise<UserRow[]> {
@@ -206,11 +223,30 @@ export async function updateUserRoles(actorId: number, targetUid: number, roles:
206
223
  await t.table("sys_user_role").insert({ user_id: targetUid, role_name: role }).run();
207
224
  });
208
225
  invalidateUser(Number(targetUid));
226
+ // 提权类变更(牵涉 admin / superadmin)单列严重级:任命管理员=把该租户的最高权限交出去,
227
+ // 不该和「勾了个 editor」落在同一噪声水平上。收回也算——撤销提权同样是安全事件。
228
+ const touchesPrivilege = (r: string) => r === "admin" || r === "superadmin";
229
+ const elevated = next.some(touchesPrivilege) || [...beforeSet].some(touchesPrivilege);
209
230
  await recordOp({
210
231
  action: "user.roles.update",
211
232
  module: "framework",
212
233
  target: await usernameOf(Number(targetUid)),
213
- detail: { added: next.filter(r => !beforeSet.has(r)), removed: [...beforeSet].filter(r => !next.includes(r)) },
234
+ detail: {
235
+ added: next.filter(r => !beforeSet.has(r)),
236
+ removed: [...beforeSet].filter(r => !next.includes(r)),
237
+ // —— 租户维度 ——
238
+ // 每个租户都有自己的管理员,且**用户名撞车**(都叫 admin):只记 username 分不清
239
+ // 「哪个租户的 admin 任命了谁」。sys_oplog.tenant_id 虽由请求租户注入,但那是隐式列,
240
+ // 显式落一份 + 双方 uid,审计才能直接按租户筛、也能定位到具体两个账号。
241
+ tenant: http.tenantId ?? "",
242
+ actorUid: Number(actorId),
243
+ targetUid: Number(targetUid),
244
+ // 便于直接筛出「任命/撤销管理员」这类高价值条目,不必去解析 added/removed
245
+ elevated,
246
+ },
247
+ // 4=warning(与「重设租户管理员口令」同级):提权变更按安全事件留痕;
248
+ // 普通角色变更(editor/viewer)保持默认 6=info,避免审计被噪声淹没。
249
+ level: elevated ? 4 : 6,
214
250
  });
215
251
  }
216
252
 
@@ -6,7 +6,7 @@ import { searchContacts } from "../_shared/service/domain/contacts";
6
6
  import { assertTenantSelf } from "../_shared/service/infra/access";
7
7
  import { rateLimit } from "../_shared/service/infra/rate";
8
8
 
9
- export default {
9
+ const handler = {
10
10
  // GET /api/_platform/contacts?keyword=&limit= —— 人员目录(AC1-22~24)。
11
11
  async get() {
12
12
  await respond(async () => {
@@ -23,3 +23,31 @@ export default {
23
23
  });
24
24
  },
25
25
  };
26
+
27
+ // 入参:keyword 必填(服务层红线 2~64 字符),limit 可选(钳 200)
28
+ handler.get.schema = {
29
+ query: {
30
+ type: "object",
31
+ required: ["keyword"],
32
+ properties: {
33
+ keyword: { type: "string", minLength: 2 },
34
+ limit: { type: "number" },
35
+ },
36
+ },
37
+ };
38
+
39
+ // 出参:目录红线只出 id/username/display_name(不含 email/avatar/roles)
40
+ handler.get.response = {
41
+ type: "array",
42
+ items: {
43
+ type: "object",
44
+ required: ["id", "username", "display_name"],
45
+ properties: {
46
+ id: { type: "number" },
47
+ username: { type: "string" },
48
+ display_name: { type: ["string", "null"] },
49
+ },
50
+ },
51
+ };
52
+
53
+ export default handler;
@@ -4,7 +4,7 @@ import { contactDetail } from "../../_shared/service/domain/contacts";
4
4
  import { assertTenantSelf } from "../../_shared/service/infra/access";
5
5
  import { rateLimit } from "../../_shared/service/infra/rate";
6
6
 
7
- export default {
7
+ const handler = {
8
8
  async get() {
9
9
  await respond(async () => {
10
10
  const uid = http.user?.id;
@@ -19,3 +19,27 @@ export default {
19
19
  });
20
20
  },
21
21
  };
22
+
23
+ // 入参:id 必填(详情按 id 取单条)
24
+ handler.get.schema = {
25
+ query: {
26
+ type: "object",
27
+ required: ["id"],
28
+ properties: { id: { type: "number" } },
29
+ },
30
+ };
31
+
32
+ // 出参:详情比目录多 email/avatar(安全评审 P2:仅详情端点给)
33
+ handler.get.response = {
34
+ type: "object",
35
+ required: ["id", "username", "display_name", "email", "avatar_base64"],
36
+ properties: {
37
+ id: { type: "number" },
38
+ username: { type: "string" },
39
+ display_name: { type: ["string", "null"] },
40
+ email: { type: ["string", "null"] },
41
+ avatar_base64: { type: ["string", "null"] },
42
+ },
43
+ };
44
+
45
+ export default handler;
@@ -167,7 +167,7 @@ async function listMigrationFiles(env: SolutionsEnv, name: string, version: stri
167
167
  }
168
168
 
169
169
  export function createSolutionsApi(env: SolutionsEnv) {
170
- return {
170
+ const api = {
171
171
  // GET /api/_platform/solutions/routes —— 前端归并数据源(登录即可)。
172
172
  routes: {
173
173
  async get() {
@@ -217,6 +217,65 @@ export function createSolutionsApi(env: SolutionsEnv) {
217
217
  },
218
218
  },
219
219
  };
220
+
221
+ // ---- 契约(v0.1.72:入参 .schema / 出参平级 .response)----
222
+ // 路由行出参:SolutionRouteRow(前端归并与深检共用形状)
223
+ const routeRow = {
224
+ type: "object",
225
+ required: ["path", "module", "category", "order", "title_key", "required_permission", "enabled"],
226
+ properties: {
227
+ path: { type: "string" },
228
+ module: { type: "string" },
229
+ category: { type: "string", enum: ["main", "admin", "hidden"] },
230
+ order: { type: "number" },
231
+ title_key: { type: ["string", "null"] },
232
+ required_permission: { type: ["string", "null"] },
233
+ enabled: { type: "boolean" },
234
+ },
235
+ };
236
+ api.routes.get.response = { type: "array", items: routeRow };
237
+
238
+ // 深检报告出参:VerifyReport(ok/checks/advise)
239
+ const verifyReport = {
240
+ type: "object",
241
+ required: ["ok", "checks", "advise"],
242
+ properties: {
243
+ ok: { type: "boolean" },
244
+ checks: {
245
+ type: "array",
246
+ items: {
247
+ type: "object",
248
+ required: ["id", "severity", "subject", "advise_key"],
249
+ properties: {
250
+ id: { type: "string" },
251
+ severity: { type: "string", enum: ["error", "warn", "info"] },
252
+ subject: { type: "string" },
253
+ advise_key: { type: "string" },
254
+ },
255
+ },
256
+ },
257
+ advise: { type: "array", items: { type: "string" } },
258
+ },
259
+ };
260
+ // verify:name 必填(端点门,缺参 400)
261
+ api.verify.post.schema = {
262
+ query: {
263
+ type: "object",
264
+ required: ["name"],
265
+ properties: { name: { type: "string", minLength: 1 } },
266
+ },
267
+ };
268
+ api.verify.post.response = verifyReport;
269
+ api.verify.last.get.schema = {
270
+ query: {
271
+ type: "object",
272
+ required: ["name"],
273
+ properties: { name: { type: "string", minLength: 1 } },
274
+ },
275
+ };
276
+ api.verify.last.get.response = verifyReport;
277
+
278
+ return api;
220
279
  }
221
280
 
222
281
  export default createSolutionsApi(solutionsEnv());
@@ -4,6 +4,29 @@
4
4
  import { z } from "@oj-module/runtime";
5
5
 
6
6
  export const schemas = {
7
+ "platformContactsGet": {
8
+ query: z.object({
9
+ "keyword": z.string().min(2),
10
+ "limit": z.number().optional()
11
+ }),
12
+ data: z.array(z.object({
13
+ "id": z.number(),
14
+ "username": z.string(),
15
+ "display_name": z.string().nullable()
16
+ })),
17
+ },
18
+ "platformContactsDetailGet": {
19
+ query: z.object({
20
+ "id": z.number()
21
+ }),
22
+ data: z.object({
23
+ "id": z.number(),
24
+ "username": z.string(),
25
+ "display_name": z.string().nullable(),
26
+ "email": z.string().nullable(),
27
+ "avatar_base64": z.string().nullable()
28
+ }),
29
+ },
7
30
  "platformMarketInstallPost": {
8
31
  body: z.object({
9
32
  "source": z.enum(["cdn", "file"]),
@@ -101,19 +124,6 @@ export const schemas = {
101
124
  data: z.array(z.object({
102
125
 
103
126
  }).catchall(z.unknown())),
104
- },
105
- "platformOrgTreeGet": {
106
- query: z.object({
107
- "scope": z.enum(["self", "all"]).optional()
108
- }),
109
- data: z.array(z.object({
110
- "id": z.number(),
111
- "parentId": z.number().nullable(),
112
- "name": z.string().nullable(),
113
- "nameEn": z.string().nullable().optional(),
114
- "orderNo": z.number(),
115
- "memberCount": z.number()
116
- })),
117
127
  },
118
128
  "platformOrgDepartmentsGet": {
119
129
  query: z.object({
@@ -136,6 +146,16 @@ export const schemas = {
136
146
  "page": z.number(),
137
147
  "size": z.number(),
138
148
  "truncated": z.boolean()
149
+ }),
150
+ },
151
+ "platformOrgIsMemberGet": {
152
+ query: z.object({
153
+ "scope": z.enum(["self", "all"]).optional(),
154
+ "uid": z.number().optional(),
155
+ "dept": z.number()
156
+ }),
157
+ data: z.object({
158
+ "isMember": z.boolean()
139
159
  }),
140
160
  },
141
161
  "platformOrgMembersGet": {
@@ -160,24 +180,6 @@ export const schemas = {
160
180
  "page": z.number(),
161
181
  "size": z.number(),
162
182
  "truncated": z.boolean()
163
- }),
164
- },
165
- "platformOrgUserGet": {
166
- query: z.object({
167
- "scope": z.enum(["self", "all"]).optional(),
168
- "uid": z.number().optional()
169
- }),
170
- data: z.object({
171
- "userId": z.number(),
172
- "username": z.string(),
173
- "displayName": z.string().nullable().optional(),
174
- "position": z.string().nullable(),
175
- "mainDeptId": z.number().nullable(),
176
- "mainDeptPath": z.array(z.string()),
177
- "departments": z.array(z.object({
178
- "deptId": z.number(),
179
- "deptPath": z.array(z.string())
180
- }))
181
183
  }),
182
184
  },
183
185
  "platformOrgPickerGet": {
@@ -218,14 +220,76 @@ export const schemas = {
218
220
  "userIds": z.array(z.number())
219
221
  }),
220
222
  },
221
- "platformOrgIsMemberGet": {
223
+ "platformOrgTreeGet": {
224
+ query: z.object({
225
+ "scope": z.enum(["self", "all"]).optional()
226
+ }),
227
+ data: z.array(z.object({
228
+ "id": z.number(),
229
+ "parentId": z.number().nullable(),
230
+ "name": z.string().nullable(),
231
+ "nameEn": z.string().nullable().optional(),
232
+ "orderNo": z.number(),
233
+ "memberCount": z.number()
234
+ })),
235
+ },
236
+ "platformOrgUserGet": {
222
237
  query: z.object({
223
238
  "scope": z.enum(["self", "all"]).optional(),
224
- "uid": z.number().optional(),
225
- "dept": z.number()
239
+ "uid": z.number().optional()
226
240
  }),
227
241
  data: z.object({
228
- "isMember": z.boolean()
242
+ "userId": z.number(),
243
+ "username": z.string(),
244
+ "displayName": z.string().nullable().optional(),
245
+ "position": z.string().nullable(),
246
+ "mainDeptId": z.number().nullable(),
247
+ "mainDeptPath": z.array(z.string()),
248
+ "departments": z.array(z.object({
249
+ "deptId": z.number(),
250
+ "deptPath": z.array(z.string())
251
+ }))
252
+ }),
253
+ },
254
+ "platformSolutionsRoutesGet": {
255
+ data: z.array(z.object({
256
+ "path": z.string(),
257
+ "module": z.string(),
258
+ "category": z.enum(["main", "admin", "hidden"]),
259
+ "order": z.number(),
260
+ "title_key": z.string().nullable(),
261
+ "required_permission": z.string().nullable(),
262
+ "enabled": z.boolean()
263
+ })),
264
+ },
265
+ "platformSolutionsVerifyPost": {
266
+ query: z.object({
267
+ "name": z.string().min(1)
268
+ }),
269
+ data: z.object({
270
+ "ok": z.boolean(),
271
+ "checks": z.array(z.object({
272
+ "id": z.string(),
273
+ "severity": z.enum(["error", "warn", "info"]),
274
+ "subject": z.string(),
275
+ "advise_key": z.string()
276
+ })),
277
+ "advise": z.array(z.string())
278
+ }),
279
+ },
280
+ "platformSolutionsVerifyLastGet": {
281
+ query: z.object({
282
+ "name": z.string().min(1)
283
+ }),
284
+ data: z.object({
285
+ "ok": z.boolean(),
286
+ "checks": z.array(z.object({
287
+ "id": z.string(),
288
+ "severity": z.enum(["error", "warn", "info"]),
289
+ "subject": z.string(),
290
+ "advise_key": z.string()
291
+ })),
292
+ "advise": z.array(z.string())
229
293
  }),
230
294
  },
231
295
  };